解决软件核心的复杂性
Tackling Complexity in the Heart of Software
新泽西州上萨德尔河 • 波士顿 • 印第安纳波利斯 • 旧金山
纽约 • 多伦多 • 蒙特利尔 • 伦敦 • 慕尼黑 • 巴黎 • 马德里
开普敦 • 悉尼 • 东京 • 新加坡 • 墨西哥城
Upper Saddle River, NJ • Boston • Indianapolis • San Francisco
New York • Toronto • Montreal • London • Munich • Paris • Madrid
Capetown • Sydney • Tokyo • Singapore • Mexico City
制造商和销售商用来区分其产品的许多名称均已声明为商标。本书中出现这些名称且 Addison-Wesley 知晓商标声明的地方,这些名称均以首字母大写或全部大写形式印刷。
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and Addison-Wesley was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.
作者和出版商在编写本书时已尽心尽力,但不作任何明示或暗示的保证,也不对错误或遗漏承担任何责任。对于因使用本文所含信息或程序而导致的或与之相关的偶然或间接损失,我们不承担任何责任。
The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein.
查看第517页的照片来源。
See page 517 for photo credits.
出版商为批量购买和特价销售的读者提供此书的折扣。如需更多信息,请联系:
The publisher offers discounts on this book when ordered in quantity for bulk purchases and special sales. For more information, please contact:
美国企业和政府销售
(800) 382-3419
corpsales@pearsontechgroup.com
U.S. Corporate and Government Sales
(800) 382-3419
corpsales@pearsontechgroup.com
对于美国境外的销售,请联系:
For sales outside of the U.S., please contact:
国际销售
international@pearsoned.com
International Sales
international@pearsoned.com
访问 Addison-Wesley 网站:www.awprofessional.com
Visit Addison-Wesley on the Web: www.awprofessional.com
美国国会图书馆出版品目数据
Evans, Eric, 1962–
领域驱动设计:解决软件核心的复杂性 / Eric
Evans。p
. cm。
包括参考书目和索引。ISBN
0-321-12521-5
1. 计算机软件 — 开发。2. 面向对象编程
(计算机科学)I. 标题。QA76.76.D47E82
2003
005.1—dc21
2003050331
Library of Congress Cataloging-in-Publication Data
Evans, Eric, 1962–
Domain-driven design : tackling complexity in the heart of software / Eric
Evans.
p. cm.
Includes bibliographical references and index.
ISBN 0-321-12521-5
1. Computer software—Development. 2. Object-oriented programming
(Computer science) I. Title.
QA76.76.D47E82 2003
005.1—dc21
2003050331
版权所有 © 2004 Eric Evans
Copyright © 2004 by Eric Evans
保留所有权利。未经出版商事先同意,不得以任何形式或任何手段(电子、机械、影印、录音或其他方式)复制、存储在检索系统中或传输本出版物的任何部分。在美国印刷。在加拿大同时出版。
All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. Printed in the United States of America. Published simultaneously in Canada.
有关获取使用本作品材料许可的信息,请向以下地址提交书面申请:
For information on obtaining permission for use of material from this work, please submit a written request to:
Pearson Education, Inc.
权利与合同部
500 Boylston Street, Suite 900
Boston, MA 02116
传真:(617) 671-3447
Pearson Education, Inc.
Rights and Contracts Department
500 Boylston Street, Suite 900
Boston, MA 02116
Fax: (617) 671-3447
ISBN 0-321-12521-5
文本在美国
马萨诸塞州韦斯特福德的 Courier 以再生纸印刷。
第二十一版,2015 年 7 月
ISBN 0-321-12521-5
Text printed in the United States on recycled paper at Courier in Westford,
Massachusetts.
Twenty-First printing, July 2015
“这本书值得每一位有思想的软件开发人员的书架上。”
“This book belongs on the shelf of every thoughtful software developer.”
—肯特·贝克
—Kent Beck
“埃里克·埃文斯写了一本很棒的书,讲述了如何让你的软件设计与你所要解决的问题领域的思维模型相匹配。
“Eric Evans has written a fantastic book on how you can make the design of your software match your mental model of the problem domain you are addressing.
“他的书与 XP 非常兼容。它不是关于绘制领域的图景;而是关于你如何看待它、你用什么语言来谈论它以及如何组织你的软件以反映你对它的理解不断提高。Eric 认为,了解你的问题领域很可能发生在项目结束时,也可能发生在开始时,因此重构是他技术的重要组成部分。
“His book is very compatible with XP. It is not about drawing pictures of a domain; it is about how you think of it, the language you use to talk about it, and how you organize your software to reflect your improving understanding of it. Eric thinks that learning about your problem domain is as likely to happen at the end of your project as at the beginning, and so refactoring is a big part of his technique.
“这本书读起来很有趣。埃里克讲了很多有趣的故事,而且他很有文采。我认为这本书是软件开发人员的必读书目——它是未来的经典。”
“The book is a fun read. Eric has lots of interesting stories, and he has a way with words. I see this book as essential reading for software developers—it is a future classic.”
—拉尔夫·约翰逊, 《设计模式》作者
—Ralph Johnson, author of Design Patterns
“如果你认为你在面向对象编程方面的投资没有获得价值,这本书会告诉你你忘记做什么。”
“If you don’t think you are getting value from your investment in object-oriented programming, this book will tell you what you’ve forgotten to do.”
—沃德·坎宁安
—Ward Cunningham
“Eric 成功捕捉到的是经验丰富的对象设计师一直使用的设计流程的一部分,但我们作为一个团队,在向业内其他人传达这一流程方面却非常失败。我们分享了这些知识的点点滴滴……但我们从未组织和系统化构建领域逻辑的原则。这本书很重要。”
“What Eric has managed to capture is a part of the design process that experienced object designers have always used, but that we have been singularly unsuccessful as a group in conveying to the rest of the industry. We’ve given away bits and pieces of this knowledge . . . but we’ve never organized and systematized the principles of building domain logic. This book is important.”
—Kyle Brown,《使用 IBM WebSphere 进行企业 Java 编程》一书的作者
—Kyle Brown, author of Enterprise Java Programming with IBM WebSphere
“Eric Evans 令人信服地论证了领域建模作为开发中心的重要性,并提供了实现这一目标的坚实框架和一套技术。这是永恒的智慧,在当今的方法过时后仍将长期有效。”
“Eric Evans convincingly argues for the importance of domain modeling as the central focus of development and provides a solid framework and set of techniques for accomplishing it. This is timeless wisdom, and will hold up long after the methodologies du jour have gone out of fashion.”
—Dave Collins, 《设计面向对象的用户界面》一书的作者
—Dave Collins, author of Designing Object-Oriented User Interfaces
“Eric 将现实世界中的经验建模和构建业务应用程序融入了一本实用的书籍。Eric 从值得信赖的从业者的角度撰写了这本书,书中对通用语言、与用户共享模型的好处、对象生命周期管理、逻辑和物理应用程序结构以及深度重构的过程和结果的描述是对我们领域的主要贡献。”
“Eric weaves real-world experience modeling—and building—business applications into a practical, useful book. Written from the perspective of a trusted practitioner, Eric’s descriptions of ubiquitous language, the benefits of sharing models with users, object life-cycle management, logical and physical application structuring, and the process and results of deep refactoring are major contributions to our field.”
—Luke Hohmann,《超越软件架构》一书的作者
—Luke Hohmann, author of Beyond Software Architecture
Part I Putting the Domain Model to Work
Chapter 1: Crunching Knowledge
Ingredients of Effective Modeling
Chapter 2: Communication and the Use of Language
Chapter 3: Binding Model and Implementation
Modeling Paradigms and Tool Support
Letting the Bones Show: Why Models Matter to Users
Part II The Building Blocks of a Model-Driven Design
Chapter 4: Isolating the Domain
The Domain Layer Is Where the Model Lives
Chapter 5: A Model Expressed in Software
ENTITIES (A.K.A. REFERENCE OBJECTS)
Designing the Identity Operation
Designing Associations That Involve VALUE OBJECTS
SERVICES and the Isolated Domain Layer
The Pitfalls of Infrastructure-Driven Packaging
Why the Object Paradigm Predominates
Sticking with MODEL-DRIVEN DESIGN When Mixing Paradigms
Chapter 6: The Life Cycle of a Domain Object
Choosing FACTORIES and Their Sites
When a Constructor Is All You Need
Where Does Invariant Logic Go?
ENTITY FACTORIES Versus VALUE OBJECT FACTORIES
客户端代码忽略了 R EPOSITORY实现;开发人员却不这么做
Client Code Ignores REPOSITORY Implementation; Developers Do Not
Working Within Your Frameworks
The Relationship with FACTORIES
Designing Objects for Relational Databases
Chapter 7: Using the Language: An Extended Example
Introducing the Cargo Shipping System
Isolating the Domain: Introducing the Applications
Distinguishing ENTITIES and VALUE OBJECTS
Designing Associations in the Shipping Domain
Sample Application Feature: Changing the Destination of a Cargo
Sample Application Feature: Repeat Business
FACTORIES and Constructors for Cargo
Pause for Refactoring: An Alternative Design of the Cargo AGGREGATE
Introducing a New Feature: Allocation Checking
Enhancing the Model: Segmenting the Business
Part III Refactoring Toward Deeper Insight
Epilogue: A Cascade of New Insights
Chapter 9: Making Implicit Concepts Explicit
How to Model Less Obvious Kinds of Concepts
Applying and Implementing SPECIFICATION
INTENTION-REVEALING INTERFACES
Extending SPECIFICATIONS in a Declarative Style
Draw on Established Formalisms, When You Can
Chapter 11: Applying Analysis Patterns
Chapter 12: Relating Design Patterns to the Model
Chapter 13: Refactoring Toward Deeper Insight
Chapter 14: Maintaining Model Integrity
Recognizing Splinters Within a BOUNDED CONTEXT
Testing at the CONTEXT Boundaries
Organizing and Documenting CONTEXT MAPS
Relationships Between BOUNDED CONTEXTS
CUSTOMER/SUPPLIER DEVELOPMENT TEAMS
Designing the Interface of the ANTICORRUPTION LAYER
Implementing the ANTICORRUPTION LAYER
Choosing Your Model Context Strategy
Accepting That Which We Cannot Change: Delineating the External Systems
Relationships with the External Systems
Catering to Special Needs with Distinct Models
When Your Project Is Already Under Way
Merging CONTEXTS: SEPARATE WAYS → SHARED KERNEL
Merging CONTEXTS: SHARED KERNEL → CONTINUOUS INTEGRATION
OPEN HOST SERVICE → PUBLISHED LANGUAGE
An Escalation of Distillations
The Distillation Document as Process Tool
GENERIC SUBDOMAIN Versus COHESIVE MECHANISM
When a MECHANISM Is Part of the CORE DOMAIN
Distilling to a Declarative Style
The Costs of Creating a SEGREGATED CORE
Chapter 16: Large-Scale Structure
The “Naive Metaphor” and Why We Don’t Need It
How Restrictive Should a Structure Be?
Refactoring Toward a Fitting Structure
Communication and Self-Discipline
Restructuring Yields Supple Design
Distillation Lightens the Load
Chapter 17: Bringing the Strategy Together
Combining Large-Scale Structures and BOUNDED CONTEXTS
Combining Large-Scale Structures and Distillation
Emergent Structure from Application Development
A Customer-Focused Architecture Team
Six Essentials for Strategic Design Decision Making
The Same Goes for the Technical Frameworks
很多因素都会使软件开发变得复杂。但这种复杂性的核心在于问题领域本身的复杂性。如果你试图将自动化添加到复杂的人类事务中,那么你的软件就无法避开这种复杂性——它所能做的就是控制它。
There are many things that make software development complex. But the heart of this complexity is the essential intricacy of the problem domain itself. If you’re trying to add automation to complicated human enterprise, then your software cannot dodge this complexity—all it can do is control it.
控制复杂性的关键是良好的领域模型,这种模型通过引入底层结构超越了领域的表面视野,为软件开发人员提供了他们所需的优势。良好的领域模型可能非常有价值,但这并不是一件容易的事情。很少有人能做好,而且很难教。
The key to controlling complexity is a good domain model, a model that goes beyond a surface vision of a domain by introducing an underlying structure, which gives the software developers the leverage they need. A good domain model can be incredibly valuable, but it’s not something that’s easy to make. Few people can do it well, and it’s very hard to teach.
Eric Evans 是少数能够很好地创建领域模型的人之一。我通过与他合作发现了这一点——当你找到一个比你更熟练的客户时,你会感到非常高兴。我们的合作很短暂,但非常有趣。从那时起,我们一直保持联系,我看着这本书慢慢地酝酿。
Eric Evans is one of those few who can create domain models well. I discovered this by working with him—one of those wonderful times when you find a client who’s more skilled than you are. Our collaboration was short but enormous fun. Since then we’ve stayed in touch, and I’ve watched this book gestate slowly.
等待是值得的。
It’s been well worth the wait.
这本书已经发展成为一本满足了巨大抱负的书:描述和构建关于领域建模艺术的词汇表。提供一个参考框架,通过它我们可以解释这项活动以及教授这项难以学习的技能。这本书在成型过程中给了我很多新的想法,如果连概念建模的老手都无法从阅读这本书中获得大量新想法,那我会感到惊讶。
This book has evolved into one that satisfies a huge ambition: To describe and build a vocabulary about the very art of domain modeling. To provide a frame of reference through which we can explain this activity as well as teach this hard-to-learn skill. It’s a book that’s given me many new ideas as it has taken shape, and I’d be astonished if even old hands at conceptual modeling don’t get a raft of new ideas from reading this book.
Eric 还巩固了我们多年来学到的许多东西。首先,在领域建模中,你不应该将概念与实现分开。一个有效的领域建模者不仅可以和会计师一起使用白板,还可以和程序员一起编写 Java。部分原因是你不能建立一个概念模型在不考虑实现问题的情况下非常有用。但概念和实现应该放在一起的主要原因是:领域模型的最大价值在于它提供了一种通用语言,将领域专家和技术人员联系在一起。
Eric also cements many of the things that we’ve learned over the years. First, in domain modeling, you shouldn’t separate the concepts from the implementation. An effective domain modeler can not only use a whiteboard with an accountant, but also write Java with a programmer. Partly this is true because you cannot build a useful conceptual model without considering implementation issues. But the primary reason why concepts and implementation belong together is this: The greatest value of a domain model is that it provides a ubiquitous language that ties domain experts and technologists together.
您将从本书中学到的另一个教训是,领域模型不是先建模然后实现的。像许多人一样,我开始拒绝“先设计,后构建”的分阶段思维。但 Eric 的经验告诉我们,真正强大的领域模型会随着时间的推移而发展,即使是最有经验的建模者也会发现,他们在系统首次发布后会获得最好的想法。
Another lesson you’ll learn from this book is that domain models aren’t first modeled and then implemented. Like many people, I’ve come to reject the phased thinking of “design, then build.” But the lesson of Eric’s experience is that the really powerful domain models evolve over time, and even the most experienced modelers find that they gain their best ideas after the initial releases of a system.
我认为并且希望,这将是一本具有巨大影响力的书。它将为一个非常棘手的领域增加结构和凝聚力,同时教会很多人如何使用一个有价值的工具。领域模型在控制软件开发方面可以产生重大影响——无论它们在何种语言或环境中实现。
I think, and hope, that this will be an enormously influential book. One that will add structure and cohesion to a very slippery field while it teaches a lot of people how to use a valuable tool. Domain models can have big consequences in controlling software development—in whatever language or environment they are implemented.
最后但很重要的一个想法。我最尊重这本书的一点是,埃里克不怕谈论他没有成功的时候。大多数作者都喜欢保持一种无私的全能态度。埃里克明确表示,像我们大多数人一样,他既尝过成功,也尝过失望。重要的是他可以从两者中吸取教训——对我们来说更重要的是,他可以传授他的教训。
One final yet important thought. One of things I most respect about this book is that Eric is not afraid to talk about the times when he hasn’t been successful. Most authors like to maintain an air of disinterested omnipotence. Eric makes it clear that like most of us, he’s tasted both success and disappointment. The important thing is that he can learn from both—and more important for us is that he can pass on his lessons.
马丁·福勒
2003 年 4 月
Martin Fowler
April 2003
至少 20 年来,领先的软件设计师都已将领域建模和设计视为重要主题,但令人惊讶的是,关于需要做什么或如何做的文章却少之又少。尽管从未明确阐述过,但对象社区中已涌现出一种哲学,我称之为领域驱动设计。
Leading software designers have recognized domain modeling and design as critical topics for at least 20 years, yet surprisingly little has been written about what needs to be done or how to do it. Although it has never been formulated clearly, a philosophy has emerged as an undercurrent in the object community, a philosophy I call domain-driven design.
过去十年,我在多个业务和技术领域开发了复杂的系统。在工作中,我尝试了面向对象开发领域的领导者提出的设计和开发流程最佳实践。我的一些项目非常成功,但也有一些失败了。成功项目的共同特点是丰富的领域模型,该模型通过设计迭代不断发展,并成为项目结构的一部分。
I have spent the past decade developing complex systems in several business and technical domains. In my work, I have tried best practices in design and development process as they have emerged from the leaders in object-oriented development. Some of my projects were very successful; a few failed. A feature common to the successes was a rich domain model that evolved through iterations of design and became part of the fabric of the project.
本书提供了制定设计决策的框架和讨论领域设计的技术词汇。它结合了广为接受的最佳实践以及我自己的见解和经验。面对复杂领域的软件开发团队可以使用此框架系统地进行领域驱动设计。
This book provides a framework for making design decisions and a technical vocabulary for discussing domain design. It is a synthesis of widely accepted best practices along with my own insights and experiences. Software development teams facing complex domains can use this framework to approach domain-driven design systematically.
在我的记忆中,有三个项目是领域设计实践如何显著影响开发结果的生动例子。尽管这三个项目都提供了有用的软件,但只有一个项目实现了其雄心勃勃的目标,并开发了复杂的软件,这些软件不断发展以满足组织的持续需求。
Three projects stand out in my memory as vivid examples of how dramatically domain design practice can affect development results. Although all three projects delivered useful software, only one achieved its ambitious objectives and produced complex software that continued to evolve to meet the ongoing needs of the organization.
我亲眼目睹一个项目快速启动,交付了一个实用、简单的基于 Web 的交易系统。开发人员飞速他们只能凭直觉做决定,但这并没有阻碍他们,因为简单的软件几乎不需要设计就可以编写出来。由于最初的成功,人们对未来发展的期望很高。就在那时,我被要求开发第二个版本。当我仔细查看时,我发现他们缺乏领域模型,甚至没有通用的项目语言,并且背负着非结构化的设计。项目负责人不同意我的评估,我拒绝了这份工作。一年后,团队发现自己陷入了困境,无法交付第二个版本。虽然他们对技术的使用并不出色,但业务逻辑却让他们难以招架。他们的第一个版本过早地僵化为一个高维护的遗留系统。
I watched one project get out of the gate fast, by delivering a useful, simple Web-based trading system. Developers were flying by the seat of their pants, but this didn’t hinder them because simple software can be written with little attention to design. As a result of this initial success, expectations for future development were sky-high. That is when I was asked to work on the second version. When I took a close look, I saw that they lacked a domain model, or even a common language on the project, and were saddled with an unstructured design. The project leaders did not agree with my assessment, and I declined the job. A year later, the team found itself bogged down and unable to deliver a second version. Although their use of technology was not exemplary, it was the business logic that over-came them. Their first release had ossified prematurely into a high-maintenance legacy.
要突破复杂性的上限,就需要更严肃地设计领域逻辑。在我职业生涯的早期,我很幸运地参与了一个确实强调领域设计的项目。这个项目的领域至少与第一个项目一样复杂,最初也取得了一定的成功,为机构交易者提供了一个简单的应用程序。但在这种情况下,最初的交付之后是连续的开发加速。每次迭代都为集成和完善上一版本的功能提供了令人兴奋的新选择。团队能够以灵活性和扩展能力来满足交易者的需求。这种上升轨迹直接归功于一个深刻的领域模型,该模型经过反复改进并用代码表达。随着团队对领域的新见解的增加,模型也得到了深化。不仅开发人员之间的沟通质量得到了提高,而且开发人员与领域专家之间的沟通质量也得到了提高,而且设计不仅没有带来越来越重的维护负担,而且变得更易于修改和扩展。
Lifting this ceiling on complexity calls for a more serious approach to the design of domain logic. Early in my career, I was fortunate to end up on a project that did emphasize domain design. This project, in a domain at least as complex as the first one, also started with a modest initial success, delivering a simple application for institutional traders. But in this case, the initial delivery was followed up with successive accelerations of development. Each iteration opened exciting new options for integrating and elaborating the functionality of the previous release. The team was able to respond to the needs of the traders with flexibility and expanding capability. This upward trajectory was directly attributable to an incisive domain model, repeatedly refined and expressed in code. As the team gained new insight into the domain, the model deepened. The quality of communication improved not only among developers but also between developers and domain experts, and the design—far from imposing an ever-heavier maintenance burden—became easier to modify and extend.
不幸的是,项目不能仅仅通过认真对待模型就达到这样的良性循环。我过去的一个项目开始时雄心勃勃,希望基于领域模型构建一个全球企业系统,但经过多年的失望之后,它降低了目标,陷入了常规。该团队拥有良好的工具和对业务的良好理解,并非常重视建模。但是,开发人员角色的划分选择不当,导致建模与实现脱节,因此设计无法反映正在进行的深入分析。无论如何,详细业务对象的设计不够严格,无法支持将它们结合起来在复杂的应用程序中。由于开发人员的技能水平参差不齐,反复的迭代并没有使代码得到改进,他们没有意识到创建基于模型的对象(这些对象也可以作为实用的运行软件)的非正式风格和技术。随着时间的推移,开发工作变得复杂,团队失去了对系统的凝聚力。经过多年的努力,该项目确实生产出了适度、有用的软件,但团队已经放弃了早期的雄心壮志以及对模型的关注。
Unfortunately, projects don’t arrive at such a virtuous cycle just by taking models seriously. One project from my past started with lofty aspirations to build a global enterprise system based on a domain model, but after years of disappointment, it lowered its sights and settled into conventionality. The team had good tools and a good understanding of the business, and it gave careful attention to modeling. But a poorly chosen separation of developer roles disconnected modeling from implementation, so that the design did not reflect the deep analysis that was going on. In any case, the design of detailed business objects was not rigorous enough to support combining them in elaborate applications. Repeated iteration produced no improvement in the code, due to uneven skill levels among developers, who had no awareness of the informal body of style and technique for creating model-based objects that also function as practical, running software. As months rolled by, development work became mired in complexity and the team lost its cohesive vision of the system. After years of effort, the project did produce modest, useful software, but the team had given up its early ambitions along with the model focus.
很多因素都可能使项目偏离轨道:官僚主义、目标不明确、缺乏资源等等。但设计方法在很大程度上决定了软件的复杂程度。当复杂性失控时,开发人员就无法充分理解软件,无法轻松安全地更改或扩展它。另一方面,良好的设计可以创造机会来利用这些复杂的功能。
Many things can put a project off course: bureaucracy, unclear objectives, and lack of resources, to name a few. But it is the approach to design that largely determines how complex software can become. When complexity gets out of hand, developers can no longer understand the software well enough to change or extend it easily and safely. On the other hand, a good design can create opportunities to exploit those complex features.
有些设计因素是技术性的。在网络、数据库和软件的其他技术维度的设计上投入了大量精力。关于如何解决这些问题的书籍已经很多。大批开发人员已经培养了自己的技能,并追随每一次技术进步。
Some design factors are technological. A great deal of effort has gone into the design of networks, databases, and other technical dimensions of software. Many books have been written about how to solve these problems. Legions of developers have cultivated their skills and followed each technical advancement.
然而,许多应用程序最重要的复杂性并不是技术性的。它在于领域本身,即用户的活动或业务。如果设计中没有处理好这个领域的复杂性,那么基础设施技术再好也没用。成功的设计必须系统地处理软件的这个核心方面。
Yet the most significant complexity of many applications is not technical. It is in the domain itself, the activity or business of the user. When this domain complexity is not handled in the design, it won’t matter that the infrastructural technology is well conceived. A successful design must systematically deal with this central aspect of the software.
本书的前提有两个:
The premise of this book is twofold:
1.对于大多数软件项目来说,主要关注点应该放在领域和领域逻辑上。
1. For most software projects, the primary focus should be on the domain and domain logic.
2.复杂领域设计应该基于模型。
2. Complex domain designs should be based on a model.
领域驱动设计既是一种思维方式,也是一套优先事项,旨在加速处理以下问题的软件项目:复杂领域。为了实现这一目标,本书介绍了一系列广泛的设计实践、技术和原则。
Domain-driven design is both a way of thinking and a set of priorities, aimed at accelerating software projects that have to deal with complicated domains. To accomplish that goal, this book presents an extensive set of design practices, techniques, and principles.
设计类书籍。流程类书籍。它们之间很少相互引用。每个主题本身都很复杂。这是一本设计类书籍,但我认为设计和流程密不可分。设计概念必须成功实施,否则它们就会枯竭,沦为学术讨论。
Design books. Process books. They seldom even reference each other. Each topic is complex in its own right. This is a design book, but I believe that design and process are inextricable. Design concepts must be implemented successfully or else they will dry up into academic discussion.
当人们学习设计技术时,他们会为各种可能性而兴奋。然后,一个真实项目的混乱现实降临到他们头上。他们无法将新的设计理念与必须使用的技术结合起来。或者他们不知道何时为了节省时间而放弃某个特定的设计方面,何时坚持不懈地寻找一个干净的解决方案。开发人员可以而且确实会抽象地谈论设计原则的应用,但谈论如何完成实际工作更为自然。所以,虽然这是一本设计书,但我会在需要的时候直接跨越人为的界限进入流程。这将有助于将设计原则置于上下文中。
When people learn design techniques, they feel excited by the possibilities. Then the messy realities of a real project descend on them. They can’t fit the new design ideas with the technology they must use. Or they don’t know when to let go of a particular design aspect in the interest of time and when to dig in their heels and find a clean solution. Developers can and do talk with each other abstractly about the application of design principles, but it is more natural to talk about how real things get done. So, although this is a design book, I’m going to barge right across that artificial boundary into process when I need to. This will help put design principles in context.
本书并不局限于某一特定方法,而是面向“敏捷开发流程”这一新类别。具体来说,本书假设项目已采用几种实践。这两项实践是应用本书中的方法的先决条件。
This book is not tied to a particular methodology, but it is oriented toward the new family of “Agile development processes.” Specifically, it assumes that a couple of practices are in place on the project. These two practices are prerequisites for applying the approach in this book.
1. 开发是迭代的。迭代开发已被提倡和实践了几十年,它是敏捷开发方法的基石。在敏捷开发和极限编程(或 XP)的文献中有很多很好的讨论,其中包括《面向对象项目的生存》(Cockburn 1998)和《极限编程解析》(Beck 1999)。
1. Development is iterative. Iterative development has been advocated and practiced for decades, and it is a cornerstone of Agile development methods. There are many good discussions in the literature of Agile development and Extreme Programming (or XP), among them, Surviving Object-Oriented Projects (Cockburn 1998) and Extreme Programming Explained (Beck 1999).
2. 开发人员和领域专家之间有着密切的关系。领域驱动设计将大量的知识整合成一个模型,该模型反映了对领域的深刻洞察和对关键概念的关注。这是了解领域的人和知道如何构建软件的人之间的协作。因为开发是迭代的,所以这种协作必须贯穿整个项目的生命周期。
2. Developers and domain experts have a close relationship. Domain-driven design crunches a huge amount of knowledge into a model that reflects deep insight into the domain and a focus on the key concepts. This is a collaboration between those who know the domain and those who know how to build software. Because development is iterative, this collaboration must continue throughout the project’s life.
极限编程由 Kent Beck、Ward Cunningham 等人构想(参见《极限编程解析》 [ Beck 2000 ]),是敏捷流程中最突出的流程,也是我最常接触的流程。为了使解释更加具体,我将在本书中以 XP 为基础讨论设计与流程之间的相互作用。书中所阐述的原则很容易适用于其他敏捷流程。
Extreme Programming, conceived by Kent Beck, Ward Cunningham, and others (see Extreme Programming Explained [Beck 2000]), is the most prominent of the Agile processes and the one I have worked with most. Throughout this book, to make explanations concrete, I will use XP as the basis for discussion of the interaction of design and process. The principles illustrated are easily adapted to other Agile processes.
近年来,人们开始反对复杂的开发方法,因为这种方法会给项目带来无用的静态文档和繁琐的前期规划和设计负担。相反,XP 等敏捷流程强调应对变化和不确定性的能力。
In recent years there has been a rebellion against elaborate development methodologies that burden projects with useless, static documents and obsessive upfront planning and design. Instead, the Agile processes, such as XP, emphasize the ability to cope with change and uncertainty.
极限编程认识到设计决策的重要性,但它强烈反对预先设计。相反,它付出了令人钦佩的努力来沟通并提高项目快速改变方向的能力。凭借这种反应能力,开发人员可以在项目的任何阶段使用“最简单的可行方法”,然后不断重构,进行许多小的设计改进,最终实现满足客户真正需求的设计。
Extreme Programming recognizes the importance of design decisions, but it strongly resists upfront design. Instead, it puts an admirable effort into communication and improving the project’s ability to change course rapidly. With that ability to react, developers can use the “simplest thing that could work” at any stage of a project and then continuously refactor, making many small design improvements, ultimately arriving at a design that fits the customer’s true needs.
这种极简主义是设计爱好者们过度放纵的迫切需要的解药。项目被繁琐且毫无价值的文档所拖累。他们患上了“分析瘫痪症”,团队成员太害怕设计不完美,以至于他们根本没有取得任何进展。必须有所改变。
This minimalism has been a much-needed antidote to some of the excesses of design enthusiasts. Projects have been bogged down by cumbersome documents that provided little value. They have suffered from “analysis paralysis,” with team members so afraid of an imperfect design that they made no progress at all. Something had to change.
不幸的是,其中一些流程理念可能会被误解。每个人对“最简单”的定义都不同。持续重构是一系列小规模的重新设计;没有坚实设计原则的开发人员将生成难以理解或更改的代码库——这与敏捷性背道而驰。尽管对意外需求的恐惧往往会导致过度工程,但尝试避免过度设计可能会发展成另一种恐惧:害怕进行任何深度设计思考。
Unfortunately, some of these process ideas can be misinterpreted. Each person has a different definition of “simplest.” Continuous refactoring is a series of small redesigns; developers without solid design principles will produce a code base that is hard to understand or change—the opposite of agility. And although fear of unanticipated requirements often leads to overengineering, the attempt to avoid overengineering can develop into another fear: a fear of doing any deep design thinking at all.
事实上,XP 最适合具有敏锐设计意识的开发人员。XP 流程假设您可以通过重构来改进设计,并且您会经常快速地进行重构。但过去的设计选择使重构本身变得更容易或更难。XP 流程试图增加团队沟通,但模型和设计选择会澄清或混淆沟通。
In fact, XP works best for developers with a sharp design sense. The XP process assumes that you can improve a design by refactoring, and that you will do this often and rapidly. But past design choices make refactoring itself either easier or harder. The XP process attempts to increase team communication, but model and design choices clarify or confuse communication.
本书将设计和开发实践交织在一起,并说明了领域驱动设计和敏捷开发如何相互促进。在敏捷开发流程的背景下,采用复杂的领域建模方法将加速开发。流程与领域开发的相互关系使这种方法比任何“纯”设计方法都更实用。
This book intertwines design and development practice and illustrates how domain-driven design and Agile development reinforce each other. A sophisticated approach to domain modeling within the context of an Agile development process will accelerate development. The interrelationship of process with domain development makes this approach more practical than any treatment of “pure” design in a vacuum.
本书分为四个主要部分:
The book is divided into four major sections:
第一部分:让领域模型发挥作用介绍了领域驱动开发的基本目标;这些目标激发了后面章节的实践。由于软件开发方法多种多样,第一部分定义了一些术语,并概述了使用领域模型来推动通信和设计的含义。
Part I: Putting the Domain Model to Work presents the basic goals of domain-driven development; these goals motivate the practices in later sections. Because there are so many approaches to software development, Part I defines terms and gives an overview of the implications of using the domain model to drive communication and design.
第二部分:模型驱动设计的构建块将面向对象领域建模的最佳实践核心浓缩为一组基本构建块。本节重点介绍如何弥合模型与实际运行软件之间的差距。共享这些标准模式可使设计井然有序。团队成员可以更轻松地理解彼此的工作。使用标准模式还可以为通用语言提供术语,所有团队成员都可以使用该语言来讨论模型和设计决策。
Part II: The Building Blocks of a Model-Driven Design condenses a core of best practices in object-oriented domain modeling into a set of basic building blocks. This section focuses on bridging the gap between models and practical, running software. Sharing these standard patterns brings order to the design. Team members more easily understand each other’s work. Using standard patterns also contributes terminology to a common language, which all team members can use to discuss model and design decisions.
但本节的重点是关注那些使模型和实现保持一致的决策类型,它们相互增强对方的有效性。这种一致性需要关注单个元素的细节。在这种小规模上精心制作为开发人员提供了坚实的基础,使他们能够应用第三部分和第四部分的建模方法。
But the main point of this section is to focus on the kinds of decisions that keep the model and implementation aligned with each other, each reinforcing the other’s effectiveness. This alignment requires attention to the detail of individual elements. Careful crafting at this small scale gives developers a steady foundation from which to apply the modeling approaches of Parts III and IV.
第三部分:重构以获得更深层次的洞察力,它超越了构建模块的范畴,而将模块组装成能够产生回报的实用模型。本节强调发现过程,而不是直接跳到深奥的设计原则中。有价值的模型不会立即出现;它们需要对领域有深刻的理解。这种理解来自于深入研究,基于可能简单的模型实现初始设计,然后一次又一次地对其进行转换。每次团队获得洞察力时,模型都会被转换以揭示更丰富的知识,代码会被重构以反映更深层次的模型并使其潜力可供应用程序使用。然后,偶尔,这种剥洋葱的过程会带来突破更深层模型的机会,随之而来的是一系列深刻的设计变化。
Part III: Refactoring Toward Deeper Insight goes beyond the building blocks to the challenge of assembling them into practical models that provide the payoff. Rather than jumping directly into esoteric design principles, this section emphasizes the discovery process. Valuable models do not emerge immediately; they require a deep understanding of the domain. That understanding comes from diving in, implementing an initial design based on a probably naive model, and then transforming it again and again. Each time the team gains insight, the model is transformed to reveal that richer knowledge, and the code is refactored to reflect the deeper model and make its potential available to the application. Then, once in a while, this onion peeling leads to an opportunity to break through to a much deeper model, attended by a rush of profound design changes.
探索本质上是开放的,但不一定非是随机的。第三部分深入探讨了可以指导选择过程的建模原则,以及有助于指导搜索的技术。
Exploration is inherently open-ended, but it does not have to be random. Part III delves into modeling principles that can guide choices along the way, and techniques that help direct the search.
第四部分:战略设计处理复杂系统、大型组织以及与外部系统和遗留系统的交互中出现的情况。本节探讨了适用于整个系统的三项原则:背景、提炼和大规模结构。战略设计决策由团队甚至团队之间做出。战略设计使第一部分的目标能够在更大的范围内实现,适用于大型系统或适合庞大的企业范围网络的应用程序。
Part IV: Strategic Design deals with situations that arise in complex systems, larger organizations, and interactions with external systems and legacy systems. This section explores a triad of principles that apply to the system as a whole: context, distillation, and large-scale structure. Strategic design decisions are made by teams, or even among teams. Strategic design enables the goals of Part I to be realized on a larger scale, for a big system or an application that fits into a sprawling, enterprise-wide network.
整本书的讨论都不是通过过于简单的“玩具”问题来阐述,而是通过改编自实际项目的现实例子来阐述。
Throughout the book, discussions are illustrated not with over-simplified, “toy” problems, but with realistic examples adapted from actual projects.
本书的大部分内容都是以“模式”的形式编写的。读者应该能够理解这些内容,而不必担心这一点。设备,但对图案的风格和格式感兴趣的人可能想要阅读附录。
Much of the book is written as a set of “patterns.” Readers should be able to understand the material without concern about this device, but those who are interested in the style and format of the patterns may want to read the appendix.
补充材料可以在http://domaindrivendesign.org找到,其中包括额外的示例代码和社区讨论。
Supplemental materials can be found at http://domaindrivendesign.org, including additional example code and community discussion.
本书主要面向面向对象软件的开发人员。软件项目团队的大多数成员都可以从本书的某些部分受益。对于目前参与项目、在项目过程中尝试做这些事情的人以及已经对此类项目有丰富经验的人来说,这本书最有意义。
This book is written primarily for developers of object-oriented software. Most members of a software project team can benefit from some parts of the book. It will make the most sense to people who are currently involved with a project, trying to do some of these things as they go through, and to people who already have deep experience with such projects.
要从本书中受益,需要具备一些面向对象建模的知识。示例包括 UML 图和 Java 代码,因此能够基本阅读这些语言很重要,但无需掌握其中任何一种语言的细节。极限编程的知识将为开发过程的讨论增添视角,但这些材料对于没有背景知识的人来说应该是可以理解的。
Some knowledge of object-oriented modeling is necessary to benefit from this book. The examples include UML diagrams and Java code, so the ability to read those languages at a basic level is important, but it is unnecessary to have mastered the details of either. Knowledge of Extreme Programming will add perspective to the discussions of development process, but the material should be understandable to those without background knowledge.
对于中级软件开发人员(已经了解一些面向对象设计并且可能读过一两本软件设计书籍的读者),本书将填补空白并提供有关对象建模如何融入软件项目实际生活的观点。本书将帮助中级开发人员学习如何将复杂的建模和设计技能应用于实际问题。
For intermediate software developers—readers who already know something of object-oriented design and may have read one or two software design books—this book will fill in gaps and provide perspective on how object modeling fits into real life on a software project. The book will help intermediate developers learn to apply sophisticated modeling and design skills to practical problems.
高级或专业软件开发人员会对本书处理该领域的综合框架感兴趣。这种系统化的设计方法将帮助技术领导者引导他们的团队走上这条道路。此外,全书使用的连贯术语将帮助高级开发人员与同行进行交流。
Advanced or expert software developers will be interested in the book’s comprehensive framework for dealing with the domain. This systematic approach to design will help technical leaders guide their teams down this path. Also, the coherent terminology used throughout the book will help advanced developers communicate with their peers.
本书是一本叙事性书籍,可以从头到尾阅读,也可以从任何一章的开头开始阅读。不同背景的读者可能希望以不同的方式阅读本书,但我建议所有读者都从第一部分的介绍以及第一章开始阅读。除此之外,核心内容可能是第 2 章、第 3 章、第 9 章和第14 章。已经对主题有所了解的读者应该能够通过阅读标题和粗体文本来了解要点。非常高级的读者可能想要浏览第 I 部分和第 II部分,并且可能对第 III 部分和第 IV部分最感兴趣。
This book is a narrative, and it can be read from beginning to end, or from the beginning of any chapter. Readers of various backgrounds may wish to take different paths through the book, but I do recommend that all readers start with the introduction to Part I, as well as Chapter 1. Beyond that, the core is probably Chapters 2, 3, 9, and 14. A skimmer who already has some grasp of a topic should be able to pick up the main points by reading headings and bold text. A very advanced reader may want to skim Parts I and II and will probably be most interested in Parts III and IV.
除了这些核心读者之外,分析师和相对技术型的项目经理也会从阅读本书中受益。分析师可以利用模型和设计之间的联系,在敏捷项目中做出更有效的贡献。分析师还可以使用一些战略设计原则来更好地集中精力和组织他们的工作。
In addition to this core readership, analysts and relatively technical project managers will also benefit from reading the book. Analysts can draw on the connection between model and design to make more effective contributions in the context of an Agile project. Analysts may also use some of the principles of strategic design to better focus and organize their work.
项目经理应该关注如何提高团队效率,更专注于设计对业务专家和用户有意义的软件。由于战略设计决策与团队组织和工作风格相互关联,这些设计决策必然涉及项目的领导,并对项目的轨迹产生重大影响。
Project managers should be interested in the emphasis on making a team more effective and more focused on designing software meaningful to business experts and users. And because strategic design decisions are interrelated with team organization and work styles, these design decisions necessarily involve the leadership of the project and have a major impact on the project’s trajectory.
虽然了解领域驱动设计的单个开发人员将获得宝贵的设计技术和视角,但最大的收获来自于团队联合起来应用领域驱动设计方法并将领域模型移至项目讨论中心。通过这样做,团队成员将共享一种语言,丰富他们的交流并将其与软件保持联系。他们将根据模型生成清晰的实现,为应用程序开发提供杠杆作用。他们将共享不同团队的设计工作如何相互关联的地图,并将系统地关注对组织最独特和最有价值的功能。
Although an individual developer who understands domain-driven design will gain valuable design techniques and perspective, the biggest gains come when a team joins together to apply a domain-driven design approach and to move the domain model to the project’s center of discourse. By doing so, the team members will share a language that enriches their communication and keeps it connected to the software. They will produce a lucid implementation in step with a model, giving leverage to application development. They will share a map of how the design work of different teams relates, and they will systematically focus attention on the features that are most distinctive and valuable to the organization.
领域驱动设计是一项艰巨的技术挑战,但它能够带来巨大的回报,在大多数软件项目开始僵化为遗留系统时带来机遇。
Domain-driven design is a difficult technical challenge that can pay off big, opening opportunities just when most software projects begin to ossify into legacy.
我以各种形式写作这本书已经四年多了,在这一过程中,有许多人给予过我的帮助和支持。
I have been working on this book, in one form or another, for more than four years, and many people have helped and supported me along the way.
我感谢那些阅读手稿并发表评论的人。如果没有他们的反馈,这本书根本不可能问世。一些人对他们的评论给予了特别慷慨的关注。由 Russ Rufer 和 Tracy Bialek 领导的硅谷模式小组花了七个星期仔细审查了本书的第一稿。由 Ralph Johnson 领导的伊利诺伊大学阅读小组也花了几个星期审阅了后来的草稿。聆听这些小组的长时间、热烈的讨论产生了深远的影响。Kyle Brown 和 Martin Fowler 提供了详细的反馈、宝贵的见解和无价的精神支持(坐在鱼上)。Ward Cunningham 的评论帮助我弥补了一些重要的弱点。Alistair Cockburn 从一开始就鼓励我,并帮助我找到出版过程的方法,Hilary Evans 也是如此。David Siegel 和 Eugene Wallingford 帮助我避免在技术性较强的部分出丑。Vibhu Mohindra 和 Vladimir Gitlevich 煞费苦心地检查了所有的代码示例。
I thank those people who have read manuscripts and commented. This book would simply not have been possible without that feedback. A few have given their reviews especially generous attention. The Silicon Valley Patterns Group, led by Russ Rufer and Tracy Bialek, spent seven weeks scrutinizing the first complete draft of the book. The University of Illinois reading group led by Ralph Johnson also spent several weeks reviewing a later draft. Listening to the long, lively discussions of these groups had a profound effect. Kyle Brown and Martin Fowler contributed detailed feedback, valuable insights, and invaluable moral support (while sitting on a fish). Ward Cunningham’s comments helped me shore up some important weak points. Alistair Cockburn encouraged me early on and helped me find my way through the publication process, as did Hilary Evans. David Siegel and Eugene Wallingford have helped me avoid embarrassing myself in the more technical parts. Vibhu Mohindra and Vladimir Gitlevich painstakingly checked all the code examples.
Rob Mee 阅读了我早期对该材料的一些探索,并在我摸索着如何传达这种设计风格时与我集思广益。然后他与我一起仔细研究了后来的草稿。
Rob Mee read some of my earliest explorations of the material, and brainstormed ideas with me when I was groping for some way to communicate this style of design. He then pored over a much later draft with me.
乔希·克里夫斯基 (Josh Kerievsky) 是本书发展过程中一个重大转折点的推动者:他说服我尝试“亚历山大”格式,这成为本书组织的核心。在密集的“指导”期间,他还帮助我第一次将第二部分中的一些材料整合成一个连贯的形式。1999 年 PLoP 会议之前的流程。这成为了本书其余部分形成的一颗种子。
Josh Kerievsky is responsible for one of the major turning points in the book’s development: He persuaded me to try out the “Alexandrian” pattern format, which became so central to the book’s organization. He also helped me to bring together some of the material now in Part II into a coherent form for the first time, during the intensive “shepherding” process preceding the PLoP conference in 1999. This became a seed around which much of the rest of the book formed.
我还要感谢阿瓦德·法杜尔,他让我在他美妙的咖啡馆里坐下来写作了数百个小时。那次休养,加上大量的风帆冲浪,帮助我继续前进。
Also I thank Awad Faddoul for the hundreds of hours I sat writing in his wonderful café. That retreat, along with a lot of windsurfing, helped me keep going.
我非常感谢 Martine Jousset、Richard Paselk 和 Ross Venables 拍摄了一些精美的照片来阐明一些关键概念(请参阅第 517页的照片来源)。
And I’m very grateful to Martine Jousset, Richard Paselk, and Ross Venables for creating some beautiful photographs to illustrate a few key concepts (see photo credits on page 517).
在构思这本书之前,我必须形成自己对软件开发的看法和理解。这一形成很大程度上要归功于几位才华横溢的人的慷慨帮助,他们既是我的非正式导师,也是我的朋友。David Siegel、Eric Gold 和 Iseult White 以不同的方式帮助我形成了对软件设计的思考方式。同时,Bruce Gordon、Richard Freyberg 和 Judith Segal 博士也以不同的方式帮助我在成功的项目工作中找到了自己的道路。
Before I could have conceived of this book, I had to form my view and understanding of software development. That formation owed a lot to the generosity of a few brilliant people who acted as informal mentors to me, as well as friends. David Siegel, Eric Gold, and Iseult White, each in a different way, helped me develop my way of thinking about software design. Meanwhile, Bruce Gordon, Richard Freyberg, and Dr. Judith Segal, also in very different ways, helped me find my way in the world of successful project work.
我自己的想法自然而然地从当时流行的各种想法中产生。其中一些贡献将在正文中清晰可见,并在可能的情况下引用。其他一些贡献是如此根本,以至于我甚至没有意识到它们对我的影响。
My own notions naturally grew out of a body of ideas in the air at that time. Some of those contributions will be clear in the main text and referenced where possible. Others are so fundamental that I don’t even realize their influence on me.
我的硕士论文导师 Bala Subramanium 博士引导我研究数学建模,我们将数学建模应用于化学反应动力学。建模就是建模,这项工作是本书的创作之路的一部分。
My master’s thesis advisor, Dr. Bala Subramanium, turned me on to mathematical modeling, which we applied to chemical reaction kinetics. Modeling is modeling, and that work was part of the path that led to this book.
甚至在那之前,我的思维方式就受到父母卡罗尔和加里·埃文斯的影响。几位特别的老师激发了我的兴趣或帮助我打下基础,尤其是戴尔·柯里尔(高中数学老师)、玛丽·布朗(高中英语写作老师)和约瑟芬·麦克格拉默里(六年级科学老师)。
Even before that, my way of thinking was shaped by my parents, Carol and Gary Evans. And a few special teachers awakened my interest or helped me lay foundations, especially Dale Currier (a high school math teacher), Mary Brown (a high school English composition teacher), and Josephine McGlamery (a sixth-grade science teacher).
最后,我要感谢我的朋友和家人,以及费尔南多·德莱昂一路以来的鼓励。
Finally, I thank my friends and family, and Fernando De Leon, for their encouragement all along the way.
这幅十八世纪的中国地图代表了整个世界。中国位于地图中心,占据了大部分空间,周围是其他国家的敷衍表示。这是适合那个有意向内转的社会的世界模型。地图所代表的世界观在与外国人打交道时肯定没有帮助。当然,它对现代中国毫无用处。地图是模型,每个模型都代表了现实的某个方面或一个有趣的想法。模型是一种简化。它是对现实的一种解释,它抽象了与解决手头问题相关的方面,并忽略了无关的细节。
This eighteenth-century Chinese map represents the whole world. In the center and taking up most of the space is China, surrounded by perfunctory representations of other countries. This was a model of the world appropriate to that society, which had intentionally turned inward. The worldview that the map represents must not have been helpful in dealing with foreigners. Certainly it would not serve modern China at all. Maps are models, and every model represents some aspect of reality or an idea that is of interest. A model is a simplification. It is an interpretation of reality that abstracts the aspects relevant to solving the problem at hand and ignores extraneous detail.
每个软件程序都与其用户的某些活动或兴趣有关。用户应用该程序的主题领域就是软件的领域。有些领域涉及物理世界:航空订票程序的领域涉及真实的人登上真实的飞机。有些领域是无形的:会计程序的领域是金钱和金融。软件领域通常与计算机关系不大,但也有例外:源代码控制系统的领域是软件开发本身。
Every software program relates to some activity or interest of its user. That subject area to which the user applies the program is the domain of the software. Some domains involve the physical world: The domain of an airline-booking program involves real people getting on real aircraft. Some domains are intangible: The domain of an accounting program is money and finance. Software domains usually have little to do with computers, though there are exceptions: The domain of a source-code control system is software development itself.
为了创建能够有效参与用户活动的软件,开发团队必须具备与这些活动相关的知识体系。所需知识的广度可能令人望而生畏。信息的数量和复杂性可能令人难以承受。模型是应对这种超负荷的工具。模型是一种经过选择性简化和有意识构建的知识形式。合适的模型可以使信息有意义并将其集中在问题上。
To create software that is valuably involved in users’ activities, a development team must bring to bear a body of knowledge related to those activities. The breadth of knowledge required can be daunting. The volume and complexity of information can be overwhelming. Models are tools for grappling with this overload. A model is a selectively simplified and consciously structured form of knowledge. An appropriate model makes sense of information and focuses it on a problem.
领域模型不是一张特定的图表,而是图表想要传达的想法。它不仅仅是领域专家头脑中的知识,而是对该知识经过严格组织和选择性抽象。图表可以表示和传达模型,就像精心编写的代码一样,就像英语句子一样。
A domain model is not a particular diagram; it is the idea that the diagram is intended to convey. It is not just the knowledge in a domain expert’s head; it is a rigorously organized and selective abstraction of that knowledge. A diagram can represent and communicate a model, as can carefully written code, as can an English sentence.
领域建模并不是要尽可能地制作“现实”的模型。即使在有形的现实世界领域,我们的模型也是人工创造的。它也不只是构建一个提供必要结果的软件机制。它更像是电影制作,松散地将现实表现为特定目的。即使是纪录片也不会展示未经编辑的现实生活。就像电影制作人选择体验的各个方面并以独特的方式呈现它们来讲述故事或表达观点一样,领域建模者选择特定模型是为了其实用性。
Domain modeling is not a matter of making as “realistic” a model as possible. Even in a domain of tangible real-world things, our model is an artificial creation. Nor is it just the construction of a software mechanism that gives the necessary results. It is more like moviemaking, loosely representing reality to a particular purpose. Even a documentary film does not show unedited real life. Just as a moviemaker selects aspects of experience and presents them in an idiosyncratic way to tell a story or make a point, a domain modeler chooses a particular model for its utility.
在领域驱动设计中,三种基本用途决定了模型的选择。
In domain-driven design, three basic uses determine the choice of a model.
1. 模型和设计的核心相互塑造。模型和实现之间的紧密联系使得模型具有相关性,并确保了对模型的分析适用于最终产品,即正在运行的程序。模型和实现之间的这种绑定也有助于维护和持续开发,因为可以根据对模型的理解来解释代码。(参见第 3 章。)
1. The model and the heart of the design shape each other. It is the intimate link between the model and the implementation that makes the model relevant and ensures that the analysis that went into it applies to the final product, a running program. This binding of model and implementation also helps during maintenance and continuing development, because the code can be interpreted based on understanding the model. (See Chapter 3.)
2. 模型是所有团队成员使用的语言的支柱。由于模型和实现的绑定,开发人员可以用这种语言讨论程序。他们可以与领域专家进行交流而无需翻译。而且由于该语言基于模型,我们的自然语言能力可以用于完善模型本身。(参见第 2 章。)
2. The model is the backbone of a language used by all team members. Because of the binding of model and implementation, developers can talk about the program in this language. They can communicate with domain experts without translation. And because the language is based on the model, our natural linguistic abilities can be turned to refining the model itself. (See Chapter 2.)
3. 模型是经过提炼的知识。模型是团队一致认可的构建领域知识和区分最受关注元素的方法。模型捕捉了我们在选择术语、分解概念和关联概念时对领域的思考方式。共享语言允许开发人员和领域专家在将信息转化为这种形式时进行有效协作。模型和实现的结合使早期版本的软件经验可以作为反馈应用于建模过程。(参见第 1 章。)
3. The model is distilled knowledge. The model is the team’s agreed-upon way of structuring domain knowledge and distinguishing the elements of most interest. A model captures how we choose to think about the domain as we select terms, break down concepts, and relate them. The shared language allows developers and domain experts to collaborate effectively as they wrestle information into this form. The binding of model and implementation makes experience with early versions of the software applicable as feedback into the modeling process. (See Chapter 1.)
接下来的三章将依次探讨这些贡献的意义和价值,以及它们之间的联系。以这些方式使用模型可以支持开发功能丰富的软件,否则这些软件将需要大量的临时开发投资。
The next three chapters set out to examine the meaning and value of each of these contributions in turn, and the ways they are intertwined. Using a model in these ways can support the development of software with rich functionality that would otherwise take a massive investment of ad hoc development.
软件的核心是它能够为用户解决与领域相关的问题。所有其他功能,尽管可能至关重要,但都支持这一基本目的。当领域很复杂时,这是一项艰巨的任务,需要有才华和技能的人集中精力。开发人员必须深入领域以积累业务知识。他们必须磨练自己的建模技能并掌握领域设计。
The heart of software is its ability to solve domain-related problems for its user. All other features, vital though they may be, support this basic purpose. When the domain is complex, this is a difficult task, calling for the concentrated effort of talented and skilled people. Developers have to steep themselves in the domain to build up knowledge of the business. They must hone their modeling skills and master domain design.
然而,这些并不是大多数软件项目的重点。大多数有才华的开发人员对学习他们所从事的特定领域没有太大兴趣,更不用说做出重大承诺来扩展他们的领域建模技能了。技术人员喜欢量化问题,以锻炼他们的技术技能。领域工作很杂乱,需要大量复杂的新知识,而这些知识似乎并不能增加计算机科学家的能力。
Yet these are not the priorities on most software projects. Most talented developers do not have much interest in learning about the specific domain in which they are working, much less making a major commitment to expand their domain-modeling skills. Technical people enjoy quantifiable problems that exercise their technical skills. Domain work is messy and demands a lot of complicated new knowledge that doesn’t seem to add to a computer scientist’s capabilities.
相反,技术人才会致力于开发复杂的框架,尝试用技术解决领域问题。而对领域的学习和建模则留给其他人。软件核心的复杂性必须迎头解决。否则,就会面临失去意义的风险。
Instead, the technical talent goes to work on elaborate frameworks, trying to solve domain problems with technology. Learning about and modeling the domain is left to others. Complexity in the heart of software has to be tackled head-on. To do otherwise is to risk irrelevance.
在一次电视脱口秀节目采访中,喜剧演员约翰·克里斯讲述了《巨蟒与圣杯》拍摄期间发生的一件事。他们一遍又一遍地拍摄一个特定的场景,但不知何故,它并不好笑。最后,他休息了一下,并与喜剧演员迈克尔·帕林(该场景的另一位演员)进行了磋商,他们想出了一个略微不同的方案。他们又拍了一次,结果很有趣,所以他们就此收工。
In a TV talk show interview, comedian John Cleese told a story of an event during the filming of Monty Python and the Holy Grail. They had been shooting a particular scene over and over, but somehow it wasn’t funny. Finally, he took a break and consulted with fellow comedian Michael Palin (the other actor in the scene), and they came up with a slight variation. They shot one more take, and it turned out funny, so they called it a day.
第二天早上,克里斯正在看电影剪辑师根据前一天的工作成果制作的粗剪版。看到他们费尽心思剪辑的场景后,克里斯发现这并不好笑;他们用了较早的镜头之一。
The next morning, Cleese was looking at the rough cut the film editor had put together of the previous day’s work. Coming to the scene they had struggled with, Cleese found that it wasn’t funny; one of the earlier takes had been used.
他问剪辑师为什么没有按照要求使用最后一条镜头。“不能用。有人走进镜头,”剪辑师回答道。克里斯看了一遍又一遍。他仍然看不出有什么问题。最后,剪辑师停下电影,指着画面边缘一瞬间可见的外套袖子。
He asked the film editor why he hadn’t used the last take, as directed. “Couldn’t use it. Someone walked in-shot,” the editor replied. Cleese watched the scene again, and then again. Still he could see nothing wrong. Finally, the editor stopped the film and pointed out a coat sleeve that was visible for a moment at the edge of the picture.
这位电影剪辑师专注于精准执行自己的专业技能。他担心其他看过这部电影的电影剪辑师会根据其技术上的完美程度来评判他的作品。在这个过程中,场景的核心已经丢失了(《克雷格·基尔伯恩深夜秀》,哥伦比亚广播公司,2001 年 9 月)。
The film editor was focused on the precise execution of his own specialty. He was concerned that other film editors who saw the movie would judge his work based on its technical perfection. In the process, the heart of the scene had been lost (“The Late Late Show with Craig Kilborn,” CBS, September 2001).
幸运的是,这个搞笑的场景被一位懂得喜剧的导演还原了。同样,当反映深刻理解的模型的开发在混乱中迷失时,团队中了解领域核心地位的领导者可以让他们的软件项目回到正轨。
Fortunately, the funny scene was restored by a director who understood comedy. In just the same way, leaders within a team who understand the centrality of the domain can put their software project back on course when development of a model that reflects deep understanding gets lost in the shuffle.
本书将展示领域开发为培养非常复杂的设计技能提供了机会。大多数领域开发的混乱软件领域的复杂性实际上是一个有趣的技术挑战。事实上,在许多科学学科中,“复杂性”是当前最令人兴奋的话题之一,因为研究人员试图解决现实世界的混乱。软件开发人员在面对从未形式化的复杂领域时也有同样的前景。创建一个清晰的模型来突破这种复杂性是令人兴奋的。
This book will show that domain development holds opportunities to cultivate very sophisticated design skills. The messiness of most software domains is actually an interesting technical challenge. In fact, in many scientific disciplines, “complexity” is one of the most exciting current topics, as researchers attempt to tackle the messiness of the real world. A software developer has that same prospect when facing a complicated domain that has never been formalized. Creating a lucid model that cuts through that complexity is exciting.
开发人员可以采用系统思维方式来寻找洞察力并生成有效的模型。有一些设计技巧可以让庞大的软件应用程序井然有序。培养这些技能可以让开发人员更有价值,即使在最初不熟悉的领域也是如此。
There are systematic ways of thinking that developers can employ to search for insight and produce effective models. There are design techniques that can bring order to a sprawling software application. Cultivation of these skills makes a developer much more valuable, even in an initially unfamiliar domain.
几年前,我开始设计一款专门用于印刷电路板 (PCB) 设计的软件工具。但有一个问题:我对电子硬件一无所知。当然,我可以接触到一些 PCB 设计师,但他们通常会让我在三分钟内头晕目眩。我怎么才能理解编写这款软件所需的知识呢?我肯定不会在交付期限之前成为一名电气工程师!
A few years ago, I set out to design a specialized software tool for printed-circuit board (PCB) design. One catch: I didn’t know anything about electronic hardware. I had access to some PCB designers, of course, but they typically got my head spinning in three minutes. How was I going to understand enough to write this software? I certainly wasn’t going to become an electrical engineer before the delivery deadline!
我们曾尝试让 PCB 设计师告诉我软件应该做什么。但这个想法很糟糕。他们是优秀的电路设计师,但他们的软件思路通常涉及读取 ASCII 文件、对其进行排序、用一些注释将其写回并生成报告。这显然不会带来他们所期望的生产力飞跃。
We tried having the PCB designers tell me exactly what the software should do. Bad idea. They were great circuit designers, but their software ideas usually involved reading in an ASCII file, sorting it, writing it back out with some annotation, and producing a report. This was clearly not going to lead to the leap forward in productivity that they were looking for.
前几次会议令人沮丧,但他们要求的报告却带来了一线希望。他们总是涉及“网络”及其各种细节。在这个领域,网络本质上是一种导线,可以连接 PCB 上任意数量的组件,并将电信号传送到与其连接的所有东西。我们有了领域模型的第一个元素。
The first few meetings were discouraging, but there was a glimmer of hope in the reports they asked for. They always involved “nets” and various details about them. A net, in this domain, is essentially a wire conductor that can connect any number of components on a PCB and carry an electrical signal to everything it is connected to. We had the first element of the domain model.
图 1.1
Figure 1.1
当我们讨论他们希望软件实现的功能时,我开始为他们绘制图表。我使用对象交互图的非正式变体来演示场景。
I started drawing diagrams for them as we discussed the things they wanted the software to do. I used an informal variant of object interaction diagrams to walk through scenarios.
图 1.2
Figure 1.2
PCB专家1:元件不一定是芯片。
PCB Expert 1: The components wouldn’t have to be chips.
开发人员(我):所以我应该称它们为“组件”吗?
Developer (Me): So I should just call them “components”?
专家 1:我们称之为“组件实例”。相同的组件可能有很多。
Expert 1: We call them “component instances.” There could be many of the same component.
专家2: “网络”框看起来就像一个组件实例。
Expert 2: The “net” box looks just like a component instance.
专家 1:他没有使用我们的符号。我想对他们来说一切都是盒子。
Expert 1: He’s not using our notation. Everything is a box for them, I guess.
开发人员:很抱歉,是的。我想我最好再解释一下这个符号。
Developer: Sorry to say, yes. I guess I’d better explain this notation a little more.
他们不断纠正我,而我也开始学习。我们消除了他们术语中的冲突和歧义以及技术观点之间的差异,他们也学到了一些东西。他们开始更精确、更一致地解释事情,我们开始一起开发一个模型。
They constantly corrected me, and as they did I started to learn. We ironed out collisions and ambiguities in their terminology and differences between their technical opinions, and they learned. They began to explain things more precisely and consistently, and we started to develop a model together.
专家 1:仅仅说信号到达参考点是不够的,我们必须知道引脚。
Expert 1: It isn’t enough to say a signal arrives at a ref-des, we have to know the pin.
开发商: Ref-des?
Developer: Ref-des?
专家 2:与组件实例相同。我们使用的特定工具将其称为 Ref-des。
Expert 2: Same thing as a component instance. Ref-des is what it’s called in a particular tool we use.
专家 1:无论如何,网络将一个实例的特定引脚连接到另一个实例的特定引脚。
Expert 1: Anyhow, a net connects a particular pin of one instance to a particular pin of another.
开发人员:您是说一个引脚只属于一个组件实例并且只连接到一个网络吗?
Developer: Are you saying that a pin belongs to only one component instance and connects to only one net?
专家 2:此外,每个网络都有一个拓扑结构,即决定网络元素连接方式的排列方式。
Expert 2: Also, every net has a topology, an arrangement that determines the way the elements of the net connect.
开发人员:好的,这样怎么样?
Developer: OK, how about this?
图 1.3
Figure 1.3
为了集中探索,我们暂时将研究范围限制在某一个特定特征上。“探测模拟”会跟踪信号的传播,以检测设计中可能存在某些问题的位置。
To focus our exploration, we limited ourselves, for a while, to studying one particular feature. A “probe simulation” would trace the propagation of a signal to detect likely sites of certain kinds of problems in the design.
开发人员:我了解信号如何通过网络传输到所有连接的引脚,但它如何传输到更远的地方?拓扑结构与此有关吗?
Developer: I understand how the signal gets carried by the Net to all the Pins attached, but how does it go any further than that? Does the Topology have something to do with it?
专家 2:不,组件会推动信号通过。
Expert 2: No. The component pushes the signal through.
开发人员:我们当然无法模拟芯片的内部行为。这太复杂了。
Developer: We certainly can’t model the internal behavior of a chip. That’s way too complicated.
专家 2:我们不必这么做。我们可以简化一下。只需列出从某些Pin到其他 Pin 的组件推送列表即可。
Expert 2: We don’t have to. We can use a simplification. Just a list of pushes through the component from certain Pins to certain others.
开发者:像这样吗?
Developer: Something like this?
[经过反复试验,我们一起勾勒出了一个场景。]
[With considerable trial-and-error, together we sketched out a scenario.]
图 1.4
Figure 1.4
Developer: But what exactly do you need to know from this computation?
专家 2:我们会寻找较长的信号延迟,例如任何超过两三跳的信号路径。这是一条经验法则。如果路径太长,信号可能无法在时钟周期内到达。
Expert 2: We’d be looking for long signal delays—say, any signal path that was more than two or three hops. It’s a rule of thumb. If the path is too long, the signal may not arrive during the clock cycle.
开发人员:超过三跳……所以我们需要计算路径长度。什么才算一跳?
Developer: More than three hops.... So we need to calculate the path lengths. And what counts as a hop?
专家 2:信号每次经过网络,就是一次跳跃。
Expert 2: Each time the signal goes over a Net, that’s one hop.
开发人员:因此我们可以传递跳数,然后网络可以增加它,就像这样。
Developer: So we could pass the number of hops along, and a Net could increment it, like this.
图 1.5
Figure 1.5
开发人员:我唯一不清楚的是“推送”来自哪里。我们是否为每个组件实例存储该数据?
Developer: The only part that isn’t clear to me is where the “pushes” come from. Do we store that data for every Component Instance?
专家 2:对于组件的所有实例,推送都是相同的。
Expert 2: The pushes would be the same for all the instances of a component.
开发人员:那么组件的类型决定了推送。每个实例的推送都一样吗?
Developer: So the type of component determines the pushes. They’ll be the same for every instance?
图 1.6
Figure 1.6
专家 2:我不太清楚这到底意味着什么,但我想象存储每个组件的推送看起来就像那样。
Expert 2: I’m not sure exactly what some of this means, but I would imagine storing push-throughs for each component would look something like that.
开发人员:抱歉,我说得太详细了。我只是想清楚。那么,现在拓扑结构与它有什么关系呢?
Developer: Sorry, I got a little too detailed there. I was just thinking it through. . . . So, now, where does the Topology come into it?
专家1:这不是用来进行探测模拟的。
Expert 1: That’s not used for the probe simulation.
开发人员:那我现在就放弃它,好吗?当我们获得这些功能时,我们可以将其恢复。
Developer: Then I’m going to drop it out for now, OK? We can bring it back when we get to those features.
就这样(这里显示的磕磕绊绊要多得多)。集思广益和改进;提问和解释。随着我对领域的理解以及他们对模型如何融入解决方案的理解,模型得到了发展。表示早期模型的类图如下所示。
And so it went (with much more stumbling than is shown here). Brainstorming and refining; questioning and explaining. The model developed along with my understanding of the domain and their understanding of how the model would play into the solution. A class diagram representing that early model looks something like this.
图 1.7
Figure 1.7
又花了几天时间研究这个,我觉得自己理解得足够多了,可以尝试写一些代码了。我编写了一个非常简单的原型,由一个自动化测试框架驱动。我避开了所有基础设施。没有持久性,也没有用户界面 (UI)。这让我可以专注于行为。我能够在短短几天内演示一个简单的探测模拟。虽然它使用了虚拟数据并将原始文本写入控制台,但它仍然使用 Java 对象来实际计算路径长度。这些 Java 对象反映了领域专家和我共享的模型。
After a couple more part-time days of this, I felt I understood enough to attempt some code. I wrote a very simple prototype, driven by an automated test framework. I avoided all infrastructure. There was no persistence, and no user interface (UI). This allowed me to concentrate on the behavior. I was able to demonstrate a simple probe simulation in just a few more days. Although it used dummy data and wrote raw text to the console, it was nonetheless doing the actual computation of path lengths using Java objects. Those Java objects reflected a model shared by the domain experts and myself.
这个原型的具体性让领域专家更清楚地了解模型的含义以及它与功能软件的关系。从那时起,我们的模型讨论变得更加互动,因为他们可以看到我如何将新获得的知识融入模型,然后融入软件。他们从原型中获得了具体的反馈,以评估自己的想法。
The concreteness of this prototype made clearer to the domain experts what the model meant and how it related to the functioning software. From that point, our model discussions became more interactive, as they could see how I incorporated my newly acquired knowledge into the model and then into the software. And they had concrete feedback from the prototype to evaluate their own thoughts.
该模型自然比此处显示的模型复杂得多,其中嵌入了与我们正在解决的问题相关的 PCB 领域的知识。它整合了许多同义词和描述中的细微变化。它排除了工程师理解但不直接相关的数百个事实,例如组件的实际数字特性。像我这样的软件专家可以查看图表,并在几分钟内开始掌握软件的用途。他或她将有一个框架来组织新信息并更快地学习,更好地猜测什么是重要的,什么是不重要的,并与 PCB 工程师更好地沟通。
Embedded in that model, which naturally became much more complicated than the one shown here, was knowledge about the domain of PCB relevant to the problems we were solving. It consolidated many synonyms and slight variations in descriptions. It excluded hundreds of facts that the engineers understood but that were not directly relevant, such as the actual digital features of the components. A software specialist like me could look at the diagrams and in minutes start to get a grip on what the software was about. He or she would have a framework to organize new information and learn faster, to make better guesses about what was important and what was not, and to communicate better with the PCB engineers.
当工程师描述他们需要的新功能时,我让他们向我介绍对象如何交互的场景。当模型对象无法帮助我们完成一个重要的场景时,我们会集思广益,提出新的想法或改变旧的想法,并利用他们的知识。我们改进了模型;代码共同进化。几个月后,PCB 工程师拥有了一个超出他们预期的丰富工具。
As the engineers described new features they needed, I made them walk me through scenarios of how the objects interacted. When the model objects couldn’t carry us through an important scenario, we brainstormed new ones or changed old ones, crunching their knowledge. We refined the model; the code coevolved. A few months later the PCB engineers had a rich tool that exceeded their expectations.
我们所做的某些事情导致了我刚才描述的成功。
Certain things we did led to the success I just described.
1. 将模型与实现绑定。这个粗略的原型在早期就形成了必要的联系,并且在所有后续迭代中都得到了维护。
1. Binding the model and the implementation. That crude prototype forged the essential link early, and it was maintained through all subsequent iterations.
2. 培养基于模型的语言。起初,工程师必须向我解释基本的 PCB 问题,我必须解释类图的含义。但随着项目的进展,我们中的任何人都可以直接从模型中取出术语,将它们组织成与模型结构一致的句子,并且无需翻译就能被清楚地理解。
2. Cultivating a language based on the model. At first, the engineers had to explain elementary PCB issues to me, and I had to explain what a class diagram meant. But as the project proceeded, any of us could take terms straight out of the model, organize them into sentences consistent with the structure of the model, and be unambiguously understood without translation.
3. 开发知识丰富的模型。对象具有行为和强制规则。模型不仅仅是一个数据模式;它是解决复杂问题不可或缺的一部分。它捕获了各种知识。
3. Developing a knowledge-rich model. The objects had behavior and enforced rules. The model wasn’t just a data schema; it was integral to solving a complex problem. It captured knowledge of various kinds.
4. 提炼模型。随着模型越来越完整,重要的概念被添加到模型中,但同样重要的是,当概念被证明没有用处或不重要时,它们就会被删除。当一个不需要的概念与一个需要的概念联系在一起时,就会发现一个新的模型,这个模型可以区分基本概念,这样就可以删除另一个概念。
4. Distilling the model. Important concepts were added to the model as it became more complete, but equally important, concepts were dropped when they didn’t prove useful or central. When an unneeded concept was tied to one that was needed, a new model was found that distinguished the essential concept so that the other could be dropped.
5. 头脑风暴和实验。语言、草图和头脑风暴的态度相结合,将我们的讨论变成了模型的实验室,在这里,可以练习、尝试和判断数百种实验性变化。当团队讨论各种场景时,口头表达本身就为所提出的模型提供了快速的可行性测试,因为耳朵可以快速检测到表达的清晰度和轻松度或尴尬程度。
5. Brainstorming and experimenting. The language, combined with sketches and a brainstorming attitude, turned our discussions into laboratories of the model, in which hundreds of experimental variations could be exercised, tried, and judged. As the team went through scenarios, the spoken expressions themselves provided a quick viability test of a proposed model, as the ear could quickly detect either the clarity and ease or the awkwardness of expression.
正是头脑风暴和大量实验的创造力,通过基于模型的语言加以利用,并通过实施反馈循环加以约束,才有可能找到一个知识丰富的模型并对其进行提炼。这种知识处理将团队的知识转化为有价值的模型。
It is the creativity of brainstorming and massive experimentation, leveraged through a model-based language and disciplined by the feedback loop through implementation, that makes it possible to find a knowledge-rich model and distill it. This kind of knowledge crunching turns the knowledge of the team into valuable models.
财务分析师处理数字。他们仔细筛选大量详细数字,将它们组合并重新组合以寻找其潜在含义,寻找一种简单的表达方式来揭示真正重要的内容——这种理解可以作为财务决策的基础。
Financial analysts crunch numbers. They sift through reams of detailed figures, combining and recombining them looking for the underlying meaning, searching for a simple presentation that brings out what is really important—an understanding that can be the basis of a financial decision.
有效的领域建模者是知识的消化者。他们从大量信息中抽取相关细流。他们尝试一个又一个有组织的想法,寻找能够理解大量信息的简单观点。许多模型被尝试、拒绝或改变。成功来自于一组新兴的抽象概念,这些概念可以理解所有细节。这种提炼是对被发现最相关的特定知识的严格表达。
Effective domain modelers are knowledge crunchers. They take a torrent of information and probe for the relevant trickle. They try one organizing idea after another, searching for the simple view that makes sense of the mass. Many models are tried and rejected or transformed. Success comes in an emerging set of abstract concepts that makes sense of all the detail. This distillation is a rigorous expression of the particular knowledge that has been found most relevant.
知识处理并不是一项单独的活动。通常由开发人员领导,由开发人员和领域专家组成的团队会进行协作。他们一起收集信息并将其处理成有用的形式。原始材料来自领域专家的想法、现有系统的用户、技术团队对相关遗留系统或同一领域其他项目的先前经验。它以项目编写或业务中使用的文档的形式出现,以及大量的讨论。早期版本或原型将经验反馈给团队并改变解释。
Knowledge crunching is not a solitary activity. A team of developers and domain experts collaborate, typically led by developers. Together they draw in information and crunch it into a useful form. The raw material comes from the minds of domain experts, from users of existing systems, from the prior experience of the technical team with a related legacy system or another project in the same domain. It comes in the form of documents written for the project or used in the business, and lots and lots of talk. Early versions or prototypes feed experience back into the team and change interpretations.
在旧的瀑布方法中,业务专家与分析师交谈,分析师消化和抽象并将结果传递给编写软件的程序员。这种方法失败了,因为它完全缺乏反馈。分析师完全负责创建模型,仅基于业务专家的意见。他们没有机会向程序员学习或获得早期版本软件的经验。知识只向一个方向流动,而不会积累。
In the old waterfall method, the business experts talk to the analysts, and analysts digest and abstract and pass the result along to the programmers, who code the software. This approach fails because it completely lacks feedback. The analysts have full responsibility for creating the model, based only on input from the business experts. They have no opportunity to learn from the programmers or gain experience with early versions of software. Knowledge trickles in one direction, but does not accumulate.
其他项目使用迭代过程,但由于没有抽象,因此无法积累知识。开发人员让专家描述所需的功能,然后他们去构建它。他们向专家展示结果并询问下一步该怎么做。如果程序员练习重构,他们可以保持软件足够干净以继续扩展它,但如果程序员对领域不感兴趣,他们只会学习应用程序应该做什么,而不是背后的原理。有用的软件可以通过这种方式构建,但项目永远不会达到强大的新功能作为旧功能的必然结果展开的地步。
Other projects use an iterative process, but they fail to build up knowledge because they don’t abstract. Developers get the experts to describe a desired feature and then they go build it. They show the experts the result and ask what to do next. If the programmers practice refactoring, they can keep the software clean enough to continue extending it, but if programmers are not interested in the domain, they learn only what the application should do, not the principles behind it. Useful software can be built that way, but the project will never arrive at a point where powerful new features unfold as corollaries to older features.
优秀的程序员自然会开始抽象和开发一个可以做更多工作的模型。但是,当这种情况只发生在技术环境中,而没有与领域专家合作时,这些概念就很幼稚。这种知识的浅薄会产生只能完成基本工作但与领域专家的思维方式缺乏深刻联系的软件。
Good programmers will naturally start to abstract and develop a model that can do more work. But when this happens only in a technical setting, without collaboration with domain experts, the concepts are naive. That shallowness of knowledge produces software that does a basic job but lacks a deep connection to the domain expert’s way of thinking.
随着所有成员一起研究模型,团队成员之间的互动也发生了变化。领域模型的不断完善迫使开发人员学习他们所协助的业务的重要原则,而不是机械地生成功能。领域专家通常会被迫将他们所知道的内容提炼为基本内容,从而完善自己的理解,并逐渐理解软件项目所需的概念严谨性。
The interaction between team members changes as all members crunch the model together. The constant refinement of the domain model forces the developers to learn the important principles of the business they are assisting, rather than to produce functions mechanically. The domain experts often refine their own understanding by being forced to distill what they know to essentials, and they come to understand the conceptual rigor that software projects require.
所有这些都使团队成员成为更有能力的知识处理者。他们筛选出无关紧要的内容。他们将模型重塑为更有用的形式。由于分析师和程序员正在向模型提供信息,因此模型组织清晰、抽象,因此可以为实施提供杠杆。由于领域专家正在向模型提供信息,因此模型反映了对业务的深入了解。抽象是真正的业务原则。
All this makes the team members more competent knowledge crunchers. They winnow out the extraneous. They recast the model into an ever more useful form. Because analysts and programmers are feeding into it, it is cleanly organized and abstracted, so it can provide leverage for the implementation. Because the domain experts are feeding into it, the model reflects deep knowledge of the business. The abstractions are true business principles.
随着模型的改进,它成为组织项目中持续流动的信息的工具。该模型专注于需求分析。它与编程和设计密切相关。在良性循环中,它加深了团队成员对领域的洞察力,让他们看得更清楚,并进一步完善模型。这些模型永远不会完美;它们会不断发展。它们必须实用且有助于理解领域。它们必须足够严谨,以使应用程序易于实现和理解。
As the model improves, it becomes a tool for organizing the information that continues to flow through the project. The model focuses requirements analysis. It intimately interacts with programming and design. And in a virtuous cycle, it deepens team members’ insight into the domain, letting them see more clearly and leading to further refinement of the model. These models are never perfect; they evolve. They must be practical and useful in making sense of the domain. They must be rigorous enough to make the application simple to implement and understand.
当我们开始编写软件时,我们永远都不知道足够多的东西。关于项目的知识是零散的,分散在许多人和文档中,并且与其他信息混杂在一起,所以我们甚至不知道我们真正需要哪些知识。看起来技术上不那么令人生畏的领域可能会具有欺骗性:我们没有意识到我们不知道多少东西。这种无知导致我们做出错误的假设。
When we set out to write software, we never know enough. Knowledge on the project is fragmented, scattered among many people and documents, and it’s mixed with other information so that we don’t even know which bits of knowledge we really need. Domains that seem less technically daunting can be deceiving: we don’t realize how much we don’t know. This ignorance leads us to make false assumptions.
与此同时,所有项目都会泄露知识。学到了一些东西的人会离开。重组会分散团队,知识又会变得支离破碎。关键子系统被外包,这样代码交付了,知识却没有交付。而使用典型的设计方法,代码和文档不会将这些来之不易的知识以可用的形式表达出来,因此当口头传统因任何原因中断时,知识就会丢失。
Meanwhile, all projects leak knowledge. People who have learned something move on. Reorganization scatters the team, and the knowledge is fragmented again. Crucial subsystems are outsourced in such a way that code is delivered but knowledge isn’t. And with typical design approaches, the code and documents don’t express this hard-earned knowledge in a usable form, so when the oral tradition is interrupted for any reason, the knowledge is lost.
高效团队有意识地增长知识,不断学习(Kerievsky 2003)。对于开发人员来说,这意味着提高技术知识,以及一般的领域建模技能(例如本书中的技能)。但这也包括认真学习他们所从事的特定领域。
Highly productive teams grow their knowledge consciously, practicing continuous learning (Kerievsky 2003). For developers, this means improving technical knowledge, along with general domain-modeling skills (such as those in this book). But it also includes serious learning about the specific domain they are working in.
这些自学成才的团队成员组成了稳定的核心团队,专注于涉及最关键领域的开发任务。(有关详细信息,请参阅第 15 章。)核心团队头脑中积累的知识使他们成为更有效的知识处理者。
These self-educated team members form a stable core of people to focus on the development tasks that involve the most critical areas. (For more on this, see Chapter 15.) The accumulated knowledge in the minds of this core team makes them more effective knowledge crunchers.
此时,停下来问自己一个问题。您是否了解了有关 PCB 设计流程的知识?尽管此示例只是对该领域的表面处理,但在讨论领域模型时应该会有一些学习。我学到了很多东西。我没有学会如何成为一名 PCB 工程师。这不是目标。我学会了与 PCB 专家交谈,了解与应用程序相关的主要概念,并对我们正在构建的内容进行健全性检查。
At this point, stop and ask yourself a question. Did you learn something about the PCB design process? Although this example has been a superficial treatment of that domain, there should be some learning when a domain model is discussed. I learned an enormous amount. I did not learn how to be a PCB engineer. That was not the goal. I learned to talk to PCB experts, understand the major concepts relevant to the application, and sanity-check what we were building.
事实上,我们的团队最终发现探测模拟的开发优先级较低,最终完全放弃了该功能。随之而去的是模型中那些捕捉信号通过组件和计数跳数理解的部分。应用程序的核心原来在其他地方,模型发生了变化,将这些方面放在了中心位置。领域专家学到了更多知识,并明确了应用程序的目标。(第 15 章深入讨论了这些问题。)
In fact, our team eventually discovered that the probe simulation was a low priority for development, and the feature was eventually dropped altogether. With it went the parts of the model that captured understanding of pushing signals through components and counting hops. The core of the application turned out to lie elsewhere, and the model changed to bring those aspects onto center stage. The domain experts had learned more and had clarified the goal of the application. (Chapter 15 discusses these issues in depth.)
即便如此,早期的工作也是必不可少的。关键的模型元素被保留了下来,但更重要的是,这项工作启动了知识处理过程,使所有后续工作都富有成效:团队成员、开发人员和领域专家获得的知识;共享语言的开始;以及通过实施完成反馈循环。探索之旅必须从某个地方开始。
Even so, the early work was essential. Key model elements were retained, but more important, that work set in motion the process of knowledge crunching that made all subsequent work effective: the knowledge gained by team members, developers, and domain experts alike; the beginnings of a shared language; and the closing of a feedback loop through implementation. A voyage of discovery has to start somewhere.
PCB 示例等模型中捕获的知识类型超出了“查找名词”的范围。业务活动和规则对于领域而言与所涉及的实体一样重要;任何领域都会有各种概念类别。知识处理可以生成反映此类洞察力的模型。在模型更改的同时,开发人员重构实现以表达模型,让应用程序使用这些知识。
The kind of knowledge captured in a model such as the PCB example goes beyond “find the nouns.” Business activities and rules are as central to a domain as are the entities involved; any domain will have various categories of concepts. Knowledge crunching yields models that reflect this kind of insight. In parallel with model changes, developers refactor the implementation to express the model, giving the application use of that knowledge.
正是在这种超越实体和价值的行动中,知识处理才变得激烈,因为业务规则之间可能存在实际的不一致。领域专家通常不知道他们的思维过程有多复杂,因为他们在工作过程中会驾驭所有这些规则,调和矛盾,并用常识填补空白。软件无法做到这一点。正是通过与软件专家密切合作进行知识处理,规则才得以澄清、充实、调和或超出范围。
It is with this move beyond entities and values that knowledge crunching can get intense, because there may be actual inconsistency among business rules. Domain experts are usually not aware of how complex their mental processes are as, in the course of their work, they navigate all these rules, reconcile contradictions, and fill in gaps with common sense. Software can’t do this. It is through knowledge crunching in close collaboration with software experts that the rules are clarified, fleshed out, reconciled, or placed out of scope.
让我们从一个非常简单的领域模型开始,它可以作为船舶航行货物预订应用程序的基础。
Let’s start with a very simple domain model that could be the basis of an application for booking cargos onto a voyage of a ship.
图 1.8
Figure 1.8
我们可以说预订应用程序的职责是将每件货物与航程关联起来,记录并跟踪这种关系。到目前为止一切顺利。在应用程序代码中的某个地方可能有这样的方法:
We can state that the booking application’s responsibility is to associate each Cargo with a Voyage, recording and tracking that relationship. So far so good. Somewhere in the application code there could be a method like this:
public int makeBooking(Cargo cargo, Voyage voyage) {
int confirmed = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmed);
返回确认;
}
public int makeBooking(Cargo cargo, Voyage voyage) {
int confirmation = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmation);
return confirmation;
}
由于总是有最后一刻取消订单的情况,航运业的标准做法是接受超过某艘船在一次航行中可以运载的货物量。这被称为“超额预订”。有时采用简单的运力百分比,例如预订 110% 的运力。在其他情况下,则采用复杂的规则,以偏向主要客户或特定类型的货物。
Because there are always last-minute cancellations, standard practice in the shipping industry is to accept more cargo than a particular vessel can carry on a voyage. This is called “overbooking.” Sometimes a simple percentage of capacity is used, such as booking 110 percent of capacity. In other cases complex rules are applied, favoring major customers or certain kinds of cargo.
这是航运领域的基本策略,航运业的任何商人都知道,但软件团队的所有技术人员可能并不了解。
This is a basic strategy in the shipping domain that would be known to any businessperson in the shipping industry, but it might not be understood by all technical people on a software team.
需求文档包含这一行:
The requirements document contains this line:
允许10%的超额预订。
Allow 10% overbooking.
类图和代码现在如下所示:
The class diagram and code now look like this:
图 1.9
Figure 1.9
public int makeBooking(Cargo cargo, Voyage voyage) {
double maxBooking = voyage.capacity() * 1.1;
if ((voyage.bookedCargoSize() + cargo.size()) > maxBooking)
返回 –1;
int confirmed = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmed);
返回 confirmed;
}
public int makeBooking(Cargo cargo, Voyage voyage) {
double maxBooking = voyage.capacity() * 1.1;
if ((voyage.bookedCargoSize() + cargo.size()) > maxBooking)
return –1;
int confirmation = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmation);
return confirmation;
}
现在,一条重要的业务规则作为保护条款隐藏在应用程序方法中。稍后,在第 4 章中,我们将研究分层架构的原理,该原理将指导我们将超额预订规则移到域对象中,但现在让我们集中精力研究如何使这些知识更加明确,并让项目中的每个人都可以访问。这将为我们带来类似的解决方案。
Now an important business rule is hidden as a guard clause in an application method. Later, in Chapter 4, we’ll look at the principle of LAYERED ARCHITECTURE, which would guide us to move the over-booking rule into a domain object, but for now let’s concentrate on how we could make this knowledge more explicit and accessible to everyone on the project. This will bring us to a similar solution.
1.如上所述,即使在开发人员的指导下,任何业务专家都不太可能读懂此代码来验证规则。
1. As written, it is unlikely that any business expert could read this code to verify the rule, even with the guidance of a developer.
2.对于技术和非业务人员来说,将需求文本与代码联系起来会很困难。
2. It would be difficult for a technical, non-businessperson to connect the requirement text with the code.
如果规则更加复杂,那么面临的风险就更大了。
If the rule were more complex, that much more would be at stake.
我们可以改变设计以更好地捕捉这些知识。超额预订规则是一种策略。策略是被称为策略( Gamma 等人,1995 )的设计模式的另一个名称。它通常是出于需要替换不同的规则,据我们所知,这里不需要这样做。但我们试图捕捉的概念确实符合策略的含义,这是领域驱动设计中同样重要的动机。(请参阅第 12 章“将设计模式与模型关联起来”。)
We can change the design to better capture this knowledge. The overbooking rule is a policy. Policy is another name for the design pattern known as STRATEGY (Gamma et al. 1995). It is usually motivated by the need to substitute different rules, which is not needed here, as far as we know. But the concept we are trying to capture does fit the meaning of a policy, which is an equally important motivation in domain-driven design. (See Chapter 12, “Relating Design Patterns to the Model.”)
图 1.10
Figure 1.10
现在的代码是:
The code is now:
public int makeBooking(Cargo cargo, Voyage voyage) {
if (!overbookingPolicy.isAllowed(cargo, voyage)) 返回 –1;
int confirmed = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmed);
返回 confirmed;
}
public int makeBooking(Cargo cargo, Voyage voyage) {
if (!overbookingPolicy.isAllowed(cargo, voyage)) return –1;
int confirmation = orderConfirmationSequence.next();
voyage.addCargo(cargo, confirmation);
return confirmation;
}
新的超额预订政策类包含这个方法:
The new Overbooking Policy class contains this method:
public boolean isAllowed(Cargo cargo, Voyage voyage) {
return (cargo.size() + voyage.bookedCargoSize()) <=
(voyage.capacity() * 1.1);
}
public boolean isAllowed(Cargo cargo, Voyage voyage) {
return (cargo.size() + voyage.bookedCargoSize()) <=
(voyage.capacity() * 1.1);
}
众所周知,超额预订是一项独特的政策,该规则的实施是明确且独立的。
It will be clear to all that overbooking is a distinct policy, and the implementation of that rule is explicit and separate.
现在,我并不建议将如此复杂的设计应用于领域的每个细节。 第 15 章“提炼”深入探讨了如何专注于重要的事情并最小化或分离其他一切。此示例旨在表明,可以使用领域模型和相应的设计来保护和共享知识。更明确的设计具有以下优势:
Now, I am not recommending that such an elaborate design be applied to every detail of the domain. Chapter 15, “Distillation,” goes into depth on how to focus on the important and minimize or separate everything else. This example is meant to show that a domain model and corresponding design can be used to secure and share knowledge. The more explicit design has these advantages:
1.为了将设计带到这个阶段,程序员和其他所有参与的人都必须了解超额预订是一项独特且重要的商业规则,而不仅仅是一种晦涩的计算。
1. In order to bring the design to this stage, the programmers and everyone else involved will have come to understand the nature of overbooking as a distinct and important business rule, not just an obscure calculation.
2.程序员可以向业务专家展示技术成果,甚至是代码,让领域专家(在指导下)能够理解,从而形成反馈回路。
2. Programmers can show business experts technical artifacts, even code, that should be intelligible to domain experts (with guidance), thereby closing the feedback loop.
有用的模型很少浮于表面。随着我们逐渐了解应用领域和需求,我们通常会丢弃最初看似重要的表面模型元素,或者改变它们的视角。微妙的抽象浮现出来,这些抽象一开始我们不会想到,但却能直击问题的核心。
Useful models seldom lie on the surface. As we come to understand the domain and the needs of the application, we usually discard superficial model elements that seemed important in the beginning, or we shift their perspective. Subtle abstractions emerge that would not have occurred to us at the outset but that pierce to the heart of the matter.
前面的例子大致基于我将在本书中引用的几个项目之一:集装箱运输系统。本书中的例子将向非航运专家开放。但在真正的项目中,团队成员需要不断学习,实用性和清晰度的模型通常要求在领域和建模技术方面都很精通。
The preceding example is loosely based on one of the projects that I’ll be drawing on for several examples throughout the book: a container shipping system. The examples in this book will be kept accessible to non-shipping experts. But on a real project, where continuous learning prepares the team members, models of utility and clarity often call for sophistication both in the domain and in modeling technique.
在那个项目中,由于货运始于货物预订,因此我们开发了一个模型,使我们能够描述货物、其行程等。这些都是必要且有用的,但领域专家却感到不满意。他们看待业务的方式是我们缺少的。
On that project, because a shipment begins with the act of booking cargo, we developed a model that allowed us to describe the cargo, its itinerary, and so on. This was all necessary and useful, yet the domain experts felt dissatisfied. There was a way they looked at their business that we were missing.
最终,经过几个月的知识积累,我们意识到货物的处理、物理装卸、从一个地方到另一个地方的移动,主要是由分包商或公司内部的运营人员进行的。在我们的航运专家看来,各方之间存在一系列的责任转移。一个流程控制着法律和实际责任的转移,从托运人到一些当地承运人,从一个承运人到另一个承运人,最后到收货人。通常,货物会在采取重要步骤时放在仓库里。在其他时候,货物会经过与航运公司业务决策无关的复杂物理步骤。而不是行程的物流中,最重要的是提单等法律文件,以及导致付款的流程。
Eventually, after months of knowledge crunching, we realized that the handling of cargo, the physical loading and unloading, the movements from place to place, was largely carried out by subcontractors or by operational people in the company. In the view of our shipping experts, there was a series of transfers of responsibility between parties. A process governed that transfer of legal and practical responsibility, from the shipper to some local carrier, from one carrier to another, and finally to the consignee. Often, the cargo would sit in a warehouse while important steps were being taken. At other times, the cargo would move through complex physical steps that were not relevant to the shipping company’s business decisions. Rather than the logistics of the itinerary, what came to the fore were legal documents such as the bill of lading, and processes leading to the release of payments.
这种对航运业务的更深层次的认识并没有导致 Itinerary 对象的删除,但模型发生了深刻的变化。我们对航运的看法从将集装箱从一个地方移动到另一个地方,转变为将货物的责任从一个实体转移到另一个实体。处理这些责任转移的功能不再笨拙地与装载操作相关联,而是由一个模型支持的,该模型源于对这些操作和这些责任之间重要关系的理解。
This deeper view of the shipping business did not lead to the removal of the Itinerary object, but the model changed profoundly. Our view of shipping changed from moving containers from place to place, to transferring responsibility for cargo from entity to entity. Features for handling these transfers of responsibility were no longer awkwardly attached to loading operations, but were supported by a model that came out of an understanding of the significant relationship between those operations and those responsibilities.
知识消化是一种探索,你不知道最终会到达哪里。
Knowledge crunching is an exploration, and you can’t know where you will end up.
领域模型可以成为软件项目通用语言的核心。该模型是项目人员头脑中建立的一组概念,其中的术语和关系反映了领域洞察力。这些术语和相互关系提供了一种语言的语义,这种语义既适合领域,又足够精确,可以进行技术开发。这是将模型编织到开发活动中并将其与代码绑定在一起的关键纽带。
A domain model can be the core of a common language for a software project. The model is a set of concepts built up in the heads of people on the project, with terms and relationships that reflect domain insight. These terms and interrelationships provide the semantics of a language that is tailored to the domain while being precise enough for technical development. This is a crucial cord that weaves the model into development activity and binds it with the code.
这种基于模型的沟通并不局限于统一建模语言 (UML) 中的图表。为了最有效地利用模型,它需要渗透到每一种沟通媒介中。它增加了书面文本文档以及敏捷流程中再次强调的非正式图表和随意对话的实用性。它通过代码本身和该代码的测试改善了沟通。
This model-based communication is not limited to diagrams in Unified Modeling Language (UML). To make most effective use of a model, it needs to pervade every medium of communication. It increases the utility of written text documents, as well as the informal diagrams and casual conversation reemphasized in Agile processes. It improves communication through the code itself and through the tests for that code.
在一个项目中,语言的使用很微妙,但却非常重要......
The use of language on a project is subtle but all-important. . . .
因为你首先写一个句子,
然后你把它切成小块;
然后把各个部分混合起来,并
在它们偶然出现时将它们整理出来:
短语的顺序
根本没有区别。
For first you write a sentence,
And then you chop it small;
Then mix the bits, and sort them out
Just as they chance to fall:
The order of the phrases makes
No difference at all.
——刘易斯·卡罗尔,《Poeta Fit,Non Nascitur》
—Lewis Carroll, “Poeta Fit, Non Nascitur”
要创建一个灵活的、知识丰富的设计,需要一种多功能的、共享的团队语言,以及在软件项目中很少发生的语言的活跃实验。
To create a supple, knowledge-rich design calls for a versatile, shared team language, and a lively experimentation with language that seldom happens on software projects.
领域专家对软件开发技术术语的理解有限,但他们会使用各自领域的术语 — 可能风格各异。另一方面,开发人员可能会以描述性、功能性的术语来理解和讨论系统,而没有专家语言所传达的含义。或者开发人员可能会创建支持其设计但领域专家无法理解的抽象概念。负责问题不同部分的开发人员会制定自己的设计概念和描述领域的方式。
Domain experts have limited understanding of the technical jargon of software development, but they use the jargon of their field—probably in various flavors. Developers, on the other hand, may understand and discuss the system in descriptive, functional terms, devoid of the meaning carried by the experts’ language. Or developers may create abstractions that support their design but are not understood by the domain experts. Developers working on different parts of the problem work out their own design concepts and ways of describing the domain.
跨越这种语言鸿沟,领域专家只能模糊地描述他们想要什么。开发人员在努力理解一个他们不熟悉的领域时,只能模糊地理解。团队中的一些成员设法掌握双语,但他们却成为信息流的瓶颈,他们的翻译也不准确。
Across this linguistic divide, the domain experts vaguely describe what they want. Developers, struggling to understand a domain new to them, vaguely understand. A few members of the team manage to become bilingual, but they become bottlenecks of information flow, and their translations are inexact.
在一个没有通用语言的项目中,开发人员必须为领域专家进行翻译。领域专家在开发人员和其他领域专家之间进行翻译。开发人员甚至会互相翻译。翻译会混淆模型概念,从而导致代码的破坏性重构。沟通的间接性掩盖了分裂的形成——不同的团队成员使用不同的术语,但没有意识到这一点。这会导致不可靠的软件无法组合在一起(参见第 14 章)。翻译工作阻碍了知识和思想的相互作用,而这些相互作用会导致深刻的模型洞察。
On a project without a common language, developers have to translate for domain experts. Domain experts translate between developers and still other domain experts. Developers even translate for each other. Translation muddles model concepts, which leads to destructive refactoring of code. The indirectness of communication conceals the formation of schisms—different team members use terms differently but don’t realize it. This leads to unreliable software that doesn’t fit together (see Chapter 14). The effort of translation prevents the interplay of knowledge and ideas that lead to deep model insights.
当语言出现问题时,项目就会面临严重问题。领域专家使用他们的专业术语,而技术团队成员则使用他们自己的语言,从设计的角度讨论领域问题。
A project faces serious problems when its language is fractured. Domain experts use their jargon while technical team members have their own language tuned for discussing the domain in terms of design.
日常讨论中的术语与代码(最终是软件项目最重要的产品)中嵌入的术语脱节。即使是同一个人在口语和书面语中使用不同的语言,因此该领域最深刻的表达通常以瞬时形式出现,而这种形式永远不会被代码甚至书面记录下来。
The terminology of day-to-day discussions is disconnected from the terminology embedded in the code (ultimately the most important product of a software project). And even the same person uses different language in speech and in writing, so that the most incisive expressions of the domain often emerge in a transient form that is never captured in the code or even in writing.
翻译阻碍了交流并使得知识消化变得乏力。
Translation blunts communication and makes knowledge crunching anemic.
然而,这些方言都不能成为通用语言,因为没有一种方言可以满足所有的需求。
Yet none of these dialects can be a common language because none serves all needs.
所有翻译的间接成本加上误解的风险实在太高了。项目需要一种比最低标准更强大的通用语言。在团队的有意识努力下,领域模型可以为该通用语言提供支撑,同时将团队沟通与软件实现联系起来。该语言可以在团队的工作中无处不在。
The overhead cost of all the translation, plus the risk of misunderstanding, is just too high. A project needs a common language that is more robust than the lowest common denominator. With a conscious effort by the team, the domain model can provide the backbone for that common language, while connecting team communication to the software implementation. That language can be ubiquitous in the team’s work.
该UBIQUITOUS LANGUAGE的词汇表包括类的名称和主要操作。该语言包括用于讨论模型中明确规定的规则的术语。它还补充了强加于模型的高级组织原则的术语(例如上下文图和大规模结构,将在第 14 章和第 16章中讨论)。最后,该语言还丰富了团队通常应用于领域模型的模式名称。
The vocabulary of that UBIQUITOUS LANGUAGE includes the names of classes and prominent operations. The LANGUAGE includes terms to discuss rules that have been made explicit in the model. It is supplemented with terms from high-level organizing principles imposed on the model (such as CONTEXT MAPS and large-scale structures, which will be discussed in Chapters 14 and 16). Finally, this language is enriched with the names of patterns the team commonly applies to the domain model.
模型关系成为所有语言的组合规则。单词和短语的含义与模型的语义相呼应。
The model relationships become the combinatory rules all languages have. The meanings of words and phrases echo the semantics of the model.
开发人员应使用基于模型的语言来描述系统中的工件,以及任务和功能。该模型应为开发人员和领域专家提供语言,以便他们相互交流,并为领域专家提供语言,以便他们就需求、开发计划和功能进行交流。语言使用得越普遍,理解就越顺畅。
The model-based language should be used among developers to describe not only artifacts in the system, but tasks and functionality. This same model should supply the language for the developers and domain experts to communicate with each other, and for the domain experts to communicate among themselves about requirements, development planning, and features. The more pervasively the language is used, the more smoothly understanding will flow.
至少,这是我们需要去的地方。但最初,模型可能还不足以胜任这些角色。它可能缺乏该领域专业术语的语义丰富性。但这些术语不能被纯粹地使用,因为它们包含歧义和矛盾。它可能缺乏开发人员在代码中创建的更微妙和更活跃的功能,要么是因为他们不认为这些是模型的一部分,要么是因为编码风格是程序性的,只隐式地包含该领域的概念。
At least, this is where we need to go. But initially the model may simply not be good enough to fill these roles. It may lack the semantic richness of the specialized jargons of the field. But those jargons can’t be used unadulterated because they contain ambiguities and contradictions. It may lack the more subtle and active features the developers have created in the code, either because they do not think of those as part of a model, or because the coding style is procedural and only implicitly carries those concepts of the domain.
但是,尽管这个顺序看起来是循环的,但能够产生更有用的模型的知识处理过程取决于团队对基于模型的语言的承诺。持续使用 UBIQUITOUS LANGUAGE将迫使模型的弱点暴露出来。团队将进行试验,并找到不方便的术语或组合的替代方案。随着语言中发现差距,新词将进入讨论。语言的这些变化将被视为领域模型的变化,并将导致团队更新类图并重命名代码中的类和方法,甚至在术语含义发生变化时改变行为。
But although the sequence seems circular, the knowledge crunching process that can produce a more useful kind of model depends on the team’s commitment to model-based language. Persistent use of the UBIQUITOUS LANGUAGE will force the model’s weaknesses into the open. The team will experiment and find alternatives to awkward terms or combinations. As gaps are found in the language, new words will enter the discussion. These changes to the language will be recognized as changes in the domain model and will lead the team to update class diagrams and rename classes and methods in the code, or even change behavior, when the meaning of a term changes.
开发人员致力于在实施过程中使用这种语言,他们会指出不精确或矛盾之处,并让领域专家参与寻找可行的替代方案。
Committed to using this language in the context of implementation, the developers will point out imprecision or contradictions, engaging the domain experts in discovering workable alternatives.
当然,领域专家会在通用语言的范围之外发言,以解释并提供更广泛的背景。但在模型涉及的范围内,他们应该使用语言,并在发现语言不合适、不完整或错误时提出顾虑。通过广泛使用基于模型的语言,直到它流畅为止,我们才能获得一个完整且易于理解的模型,该模型由简单元素组成,这些元素组合在一起可以表达复杂的想法。
Of course, domain experts will speak outside the scope of the UBIQUITOUS LANGUAGE, to explain and give broader context. But within the scope the model addresses, they should use LANGUAGE and raise concerns when they find it awkward or incomplete—or wrong. By using the model-based language pervasively and not being satisfied until it flows, we approach a model that is complete and comprehensible, made up of simple elements that combine to express complex ideas.
所以:
Therefore:
使用模型作为语言的支柱。让团队在团队内部和代码中的所有交流中坚持不懈地练习该语言。在图表、写作,尤其是演讲中使用相同的语言。
Use the model as the backbone of a language. Commit the team to exercising that language relentlessly in all communication within the team and in the code. Use the same language in diagrams, writing, and especially speech.
通过尝试其他表达方式(反映其他模型)来解决困难。然后重构代码,重命名类、方法和模块以符合新模型。解决对话中术语混淆的问题,就像我们逐渐就普通词语的含义达成共识一样。
Iron out difficulties by experimenting with alternative expressions, which reflect alternative models. Then refactor the code, renaming classes, methods, and modules to conform to the new model. Resolve confusion over terms in conversation, in just the way we come to agree on the meaning of ordinary words.
认识到UBIQUITOUS LANGUAGE的改变就是模型的改变。
Recognize that a change in the UBIQUITOUS LANGUAGE is a change to the model.
领域专家应该反对那些不恰当或不足以传达领域理解的术语或结构;开发人员应该注意会导致设计失败的歧义或不一致之处。
Domain experts should object to terms or structures that are awkward or inadequate to convey domain understanding; developers should watch for ambiguity or inconsistency that will trip up design.
有了通用语言,模型就不仅仅是一个设计工件。它成为开发人员和领域专家共同完成的所有工作不可或缺的一部分。语言以动态形式承载知识。用语言进行讨论使图表和代码背后的含义变得生动。
With a UBIQUITOUS LANGUAGE, the model is not just a design artifact. It becomes integral to everything the developers and domain experts do together. The LANGUAGE carries knowledge in a dynamic form. Discussion in the LANGUAGE brings to life the meaning behind the diagrams and code.
这篇关于UBIQUITOUS LANGUAGE的讨论假设只有一个模型在起作用。第 14 章“保持模型完整性”讨论了不同模型(和语言)的共存以及如何防止模型分裂。
This discussion of UBIQUITOUS LANGUAGE assumes that there is just one model in play. Chapter 14, “Maintaining Model Integrity,” deals with the coexistence of different models (and LANGUAGES) and how to keep a model from splintering.
通用语言是代码中未出现的设计方面的主要载体——组织整个系统的大规模结构(参见第 16 章)、定义不同系统和模型之间关系的有界上下文(参见第 14 章)以及应用于模型和设计的其他模式。
The UBIQUITOUS LANGUAGE is the primary carrier of the aspects of design that don’t appear in code—large-scale structures that organize the whole system (see Chapter 16), BOUNDED CONTEXTS that define the relationships of different systems and models (see Chapter 14), and other patterns applied to the model and design.
以下两个对话有微妙但重要的差异。在每种情况下,请注意演讲者谈论软件对业务的意义与技术工作原理的程度。用户和开发人员是否使用同一种语言?这种语言是否足够丰富,可以讨论应用程序必须做什么?
The following two dialogs have subtle, but important, differences. In each scenario, watch for how much the speakers talk about what the software means to the business versus how it works technically. Are the user and developer speaking the same language? Is that language rich enough to carry the discussion of what the application must do?
图 2.1
Figure 2.1
用户: 所以当我们改变清关点的时候,我们需要重新做整个路线计划。
User: So when we change the customs clearance point, we need to redo the whole routing plan.
开发人员:对。我们将删除货运表中所有包含该货物 ID 的行,然后将出发地、目的地和新的清关点传递到路线服务中,它将重新填充表格。我们必须在货物中有一个布尔值,这样我们就知道货运表中有数据。
Developer: Right. We’ll delete all the rows in the shipment table with that cargo id, then we’ll pass the origin, destination, and the new customs clearance point into the Routing Service, and it will re-populate the table. We’ll have to have a Boolean in the Cargo so we’ll know there is data in the shipment table.
用户:删除行?好的,随便。无论如何,如果我们之前根本没有通关点,我们也必须做同样的事情。
User: Delete the rows? OK, whatever. Anyway, if we didn’t have a customs clearance point at all before, we’ll have to do the same thing.
开发人员:当然,每当您更改出发地、目的地或清关点(或首次输入)时,我们都会检查是否有货运数据,然后将其删除,然后让路线服务重新生成。
Developer: Sure, anytime you change the origin, destination, or customs clearance point (or enter one for the first time), we’ll check to see if we have shipment data and then we’ll delete it and then let the Routing Service regenerate it.
用户:当然,如果旧的通关信息恰好是正确的,我们就不会想这么做。
User: Of course, if the old customs clearance just happened to be the right one, we wouldn’t want to do that.
开发人员:哦,没问题。让路由服务每次都重新进行加载和卸载会更简单。
Developer: Oh, no problem. It’s easier to just make the Routing Service redo the loads and unloads every time.
用户:是的,但对于我们来说,为新的行程制定所有支持计划是一项额外的工作,因此,除非变更需要,否则我们不想重新安排路线。
User: Yes, but it’s extra work for us to make all the supporting plans for a new itinerary, so we don’t want to reroute unless the change necessitates it.
开发人员: 呃。那么,如果你是第一次进入通关点,我们就必须查询表格,找到旧的派生通关点,然后将其与新的通关点进行比较。然后我们就知道是否需要重做。
Developer: Ugh. Well, then, if you are entering a customs clearance point for the first time, we’ll have to query the table to find the old derived customs clearance point, and then compare it to the new one. Then we’ll know if we need to redo it.
用户:您不必担心出发地或目的地的问题,因为行程总会改变。
User: You won’t have to worry about this on origin or destination, since the itinerary would always change then.
开发者:很好。我们不会这么做。
Developer: Good. We won’t.
图 2.2
Figure 2.2
用户:那么当我们改变清关点的时候,我们需要重新做整个路线计划。
User: So when we change the customs clearance point, we need to redo the whole routing plan.
开发人员:是的。当您更改路线规范中的任何属性时,我们将删除旧的行程,并要求路线服务根据新的路线规范生成新的行程。
Developer: Right. When you change any of the attributes in the Route Specification, we’ll delete the old Itinerary and ask the Routing Service to generate a new one based on the new Route Specification.
用户:如果我们之前根本没有指定清关点,那么我们就必须同时指定清关点。
User: If we hadn’t specified a customs clearance point at all before, we’ll have to do that at the same time.
开发人员:当然可以,只要您在路线规范中做出任何更改,我们就会重新生成行程。这包括首次输入的内容。
Developer: Sure, anytime you change anything in the Route Spec, we’ll regenerate the Itinerary. That includes entering something for the first time.
用户:当然,如果旧的通关信息恰好是正确的,我们就不会想这么做。
User: Of course, if the old customs clearance just happened to be the right one, we wouldn’t want to do that.
开发人员:哦,没问题。让路线服务每次都重新制定行程会更简单。
Developer: Oh, no problem. It’s easier to just make the Routing Service redo the Itinerary every time.
用户:是的,但为新的行程制定所有支持计划对我们来说是额外的工作,所以除非变更需要,否则我们不想重新安排路线。
User: Yes, but it’s extra work for us to make all the supporting plans for a new Itinerary, so we don’t want to reroute unless the change necessitates it.
开发人员:哦。那么我们必须在路线规范中添加一些功能。然后,每当您在规范中更改任何内容时,我们都会查看行程是否仍然满足规范。如果不满足,我们将让路线服务重新生成行程。
Developer: Oh. Then we’ll have to add some functionality to the Route Specification. Then, whenever you change anything in the Spec, we’ll see if the Itinerary still satisfies the Specification. If it doesn’t, we’ll have the Routing Service regenerate the Itinerary.
用户:您不必担心出发地或目的地的问题,因为行程总会改变。
User: You won’t have to worry about this on origin or destination, since the Itinerary would always change then.
开发人员:好的,但每次都进行比较会更简单。只有当路线规范不再满足时,才会生成行程。
Developer: Fine, but it will be simpler for us to just do the comparison every time. The Itinerary will only be generated when the Route Specification is no longer satisfied.
第二个对话更多地传达了领域专家的意图。用户在两个对话中都使用了“行程”一词,但在第二个对话中,这是一个两人可以准确、具体讨论的对象。他们明确讨论了“路线规范”,而不是每次都用属性和程序来描述它。
The second dialog conveys more of the intent of the domain expert. The user employed the word “itinerary” in both dialogs, but in the second it was an object the two could discuss precisely, concretely. They discussed the “route specification” explicitly, instead of describing it each time in terms of attributes and procedures.
这两个对话框是特意设计成彼此接近的。实际上,第一个对话框会更冗长,充斥着对应用程序功能和错误传达的解释。第二个设计基于领域模型的术语使第二个对话框更加简洁。
These two dialogs were deliberately constructed to closely parallel each other. Realistically, the first would have been more verbose, bloated with explanations of application features and miscommunications. The domain-model-based terminology of the second design makes the second dialog more concise.
语音与其他交流形式的分离是一个特别大的损失,因为我们人类在口语方面很有天赋。不幸的是,当人们说话时,他们通常不使用领域模型的语言。
The detachment of speech from other forms of communication is a particularly great loss because we humans have a genius for spoken language. Unfortunately, when people speak, they usually don’t use the language of the domain model.
这句话一开始可能听起来不太对,而且确实有例外。但下次你参加需求或设计讨论时,请认真倾听。你会听到以下描述:商业术语或行话的通俗版本。您将听到有关技术工件和具体功能的讨论。当然,您会听到来自领域模型的术语;商业术语中常用语言中的明显名词通常会被编码为对象,因此这些术语往往会被提及。但是,您是否听到过可以用当前领域模型中的关系和交互来描述的短语?
That statement may not ring true for you initially, and indeed there are exceptions. But the next time you attend a requirements or design discussion, really listen. You’ll hear descriptions of features in business jargon or layman’s versions of the jargon. You’ll hear talk about technical artifacts and concrete functionality. Sure, you’ll hear terms from the domain model; obvious nouns in the common language from the business jargon will typically be coded as objects, and so those terms will tend to be mentioned. But do you hear phrases that could even remotely be described in terms of relationships and interactions in your current domain model?
完善模型的最佳方法之一是通过语音进行探索,大声尝试各种可能的模型变体。粗糙的边缘很容易被听到。
One of the best ways to refine a model is to explore with speech, trying out loud various constructs from possible model variations. Rough edges are easy to hear.
“如果我们为路线服务提供出发地、目的地和到达时间,它就可以查找货物必须停靠的站点,然后……将它们粘贴到数据库中。”(模糊且技术性)
“If we give the Routing Service an origin, destination, and arrival time, it can look up the stops the cargo will have to make and, well . . . stick them in the database.” (vague and technical)
“出发地、目的地等等……这些信息都会输入到路线服务中,然后我们会得到一份包含了我们所需一切信息的行程单。”(更完整,但冗长)
“The origin, destination, and so on . . . it all feeds into the Routing Service, and we get back an Itinerary that has everything we need in it.” (more complete, but verbose)
“路线服务找到满足路线规范的行程。”(简洁)
“A Routing Service finds an Itinerary that satisfies a Route Specification.” (concise)
我们必须善于运用单词和短语,将我们的语言能力运用到建模工作中,就像通过绘制图表来运用我们的视觉/空间推理一样。就像我们运用我们的分析能力进行有条不紊的分析和设计,以及代码的神秘“感觉”一样。这些思维方式相辅相成,需要所有这些方式才能找到有用的模型和设计。在所有这些方式中,语言实验最常被忽视。(本书第三部分将深入探讨这一发现过程,并在几个对话中展示这种相互作用。)
It is vital that we play around with words and phrases, harnessing our linguistic abilities to the modeling effort, just as it is vital to engage our visual/spatial reasoning by sketching diagrams. Just as we employ our analytical abilities with methodical analysis and design, and that mysterious “feel” of the code. These ways of thinking complement each other, and it takes all of them to find useful models and designs. Of all of these, experimenting with language is most often overlooked. (Part III of this book will delve into this discovery process and show this interplay in several dialogs.)
事实上,我们的大脑似乎在某种程度上专门处理口语的复杂性(对于像我这样的外行人来说,一个很好的治疗方法是史蒂芬·平克的《语言本能》[ Pinker 1994 ])。例如,当不同语言背景的人聚在一起做生意时,如果他们没有共同的语言,他们就会发明一种叫做洋泾浜语的语言。洋泾浜语不像说话者的母语那样全面,但它适合手头的任务。当人们交谈时,他们自然会发现对单词的解释和含义的差异,并自然地解决他们会找出语言中的差异,并消除它们。
In fact, our brains seem to be somewhat specialized for dealing with complexity in spoken language (one good treatment for laymen, like myself, is The Language Instinct, by Steven Pinker [Pinker 1994]). For example, when people of different language backgrounds come together for commerce, if they don’t have a common language they invent one, called a pidgin. The pidgin is not as comprehensive as the speakers’ original languages, but it is suited to the task at hand. When people are talking, they naturally discover differences in interpretation and the meaning of their words, and they naturally resolve those differences. They find rough spots in the language and smooth them out.
有一次我在大学里选修了一门强化西班牙语课。课堂上的规则是,不许说一个英文单词。一开始,这让人很沮丧。感觉很不自然,需要很强的自律性。但最终,我和同学们的流利程度突破了纸上练习永远无法达到的水平。
Once I took an intensive Spanish class in college. The rule in the classroom was that not a word of English could be spoken. At first, it was frustrating. It felt very unnatural, and required a lot of self-discipline. But eventually my classmates and I broke through to a level of fluency that we could never have reached through exercises on paper.
当我们在讨论中使用领域模型的通用语言时(尤其是开发人员和领域专家讨论场景和需求的讨论),我们会更加熟练地使用这种语言,并互相传授其细微差别。我们自然而然地开始以一种在图表和文档中从未发生过的方式分享我们所说的语言。
As we use the UBIQUITOUS LANGUAGE of the domain model in discussions—especially discussions in which developers and domain experts hash out scenarios and requirements—we become more fluent in the language and teach each other its nuances. We naturally come to share the language that we speak in a way that never happens with diagrams and documents.
在软件项目中引入通用语言说起来容易做起来难,我们必须充分利用我们的天赋来实现这一目标。正如人类的视觉和空间能力让我们能够以图形概览的形式快速传达和处理信息一样,我们可以利用我们与生俱来的语法和有意义的语言天赋来推动模型开发。
Bringing about a UBIQUITOUS LANGUAGE on a software project is easier said than done, and we have to fully employ our natural talents to pull it off. Just as humans’ visual and spatial capabilities let us convey and process information rapidly in graphical overviews, we can exploit our innate talent for grammatical, meaningful language to drive model development.
因此,作为UBIQUITOUS LANGUAGE模式的附录:
Therefore, as an addendum to the UBIQUITOUS LANGUAGE pattern:
在谈论系统时,玩转模型。使用模型的元素和交互大声描述场景,以模型允许的方式组合概念。找到更简单的方法来表达您需要表达的内容,然后将这些新想法转化为图表和代码。
Play with the model as you talk about the system. Describe scenarios out loud using the elements and interactions of the model, combining concepts in ways allowed by the model. Find easier ways to say what you need to say, and then take those new ideas back down to the diagrams and code.
技术人员经常觉得有必要“保护”业务专家免受领域模型的影响。他们说:
Technical people often feel the need to “shield” the business experts from the domain model. They say:
“对他们来说太抽象了。”
“Too abstract for them.”
“他们不理解物体。”
“They don’t understand objects.”
“我们必须用他们的术语来收集需求。”
“We have to collect requirements in their terminology.”
These are just a few of the reasons I’ve heard for having two languages on the team. Forget them.
当然,设计中的一些技术组件可能与领域专家无关,但模型的核心最好能引起他们的兴趣。太抽象了?那么你怎么知道这些抽象是合理的?你对领域的理解和他们一样深刻吗?有时会从较低级别的用户那里收集特定需求,并且可能需要一些更具体的术语,但领域专家被认为能够对其领域进行深入思考。如果资深领域专家不理解该模型,那么该模型肯定有问题。
Of course there are technical components of the design that may not concern the domain experts, but the core of the model had better interest them. Too abstract? Then how do you know the abstractions are sound? Do you understand the domain as deeply as they do? Sometimes specific requirements are collected from lower-level users, and a subset of the more concrete terminology may be needed for them, but a domain expert is assumed to be capable of thinking somewhat deeply about his or her field. If sophisticated domain experts don’t understand the model, there is something wrong with the model.
现在,在开始阶段,当用户讨论尚未建模的系统未来功能时,他们没有可用的模型。但是,一旦他们开始与开发人员一起研究这些新想法,摸索共享模型的过程就开始了。它可能一开始很笨拙和不完整,但它会逐渐完善。随着新语言的发展,领域专家必须付出额外的努力来采用它,并改造任何仍然重要的旧文档。
Now at the beginning, when the users are discussing future capabilities of the system that haven’t been modeled yet, there is no model for them to use. But as soon as they begin to work through these new ideas with the developers, the process of groping toward a shared model begins. It may start out awkward and incomplete, but it will gradually get refined. As the new language evolves, the domain experts must make the extra effort to adopt it, and to retrofit any old documents that are still important.
当领域专家在与开发人员的讨论中或在他们之间使用这种语言时,他们很快会发现模型无法满足他们的需求或对他们来说似乎不正确的地方。领域专家(在开发人员的帮助下)还会发现基于模型的语言的精确性暴露了他们思维中的矛盾或模糊之处。
When domain experts use this LANGUAGE in discussions with developers or among themselves, they quickly discover areas where the model is inadequate for their needs or seems wrong to them. The domain experts (with the help of the developers) will also find areas where the precision of the model-based language exposes contradictions or vagueness in their thinking.
开发人员和领域专家可以通过逐步使用模型对象,通过场景演练来非正式地测试模型。几乎每次讨论都是开发人员和用户专家一起玩模型的机会,在过程中加深彼此的理解并完善概念。
The developers and domain experts can informally test the model by walking through scenarios, using the model objects step-by-step. Almost every discussion is an opportunity for the developers and user experts to play with the model together, deepening each other’s understanding and refining concepts as they go.
领域专家可以使用模型的语言编写用例,并且可以通过指定验收测试更直接地使用模型。
The domain experts can use the language of the model in writing use cases, and can work even more directly with the model by specifying acceptance tests.
有时会有人反对使用模型语言来收集需求。毕竟,需求不应该独立于满足需求的设计吗?这忽略了所有语言都基于某种模型的现实。单词的含义是难以捉摸的东西。领域模型通常会得出这些定义与领域专家自己的术语不同,但经过“清理”,具有更清晰、更狭窄的定义。当然,如果这些定义与该领域接受的含义不同,领域专家应该提出反对。在敏捷过程中,需求会随着项目的进行而发展,因为几乎没有知识可以预先充分指定应用程序。这种发展的一部分应该是用精致的通用语言重新构建需求。
Objections are sometimes raised to the idea of using the language of the model to collect requirements. After all, shouldn’t requirements be independent of the design that fulfills them? This overlooks the reality that all language is based on some model. The meanings of words are slippery things. The domain model will typically derive from the domain experts’ own jargon but will have been “cleaned up,” to have sharper, narrower definitions. Of course, the domain experts should object if these definitions diverge from the meanings accepted in the field. In an Agile process, requirements evolve as a project goes along because hardly ever does the knowledge exist up front to specify an application adequately. Part of this evolution should be the reframing of the requirements in the refined UBIQUITOUS LANGUAGE.
多种语言通常是必要的,但语言划分不应该在领域专家和开发人员之间进行。(第 12 章“维护模型完整性”讨论了同一项目中模型的共存。)
Multiplicity of languages is often necessary, but the linguistic division should never be between the domain experts and the developers. (Chapter 12, “Maintaining Model Integrity,” deals with the coexistence of models on the same project.)
当然,开发人员确实会使用领域专家无法理解的技术术语。开发人员需要大量的术语来讨论系统的技术方面。几乎可以肯定的是,用户也会使用超出应用程序的狭窄范围和开发人员理解范围的专业术语。但这些都是语言的扩展。这些方言不应包含反映不同模型的同一领域的替代词汇。
Of course, developers do use technical terminology that a domain expert wouldn’t understand. Developers have an extensive jargon that they need to discuss the technical aspects of a system. Almost certainly, the users will also have specialized jargon that goes well beyond the narrow scope of the application and the understanding of the developers. But these are extensions to the language. These dialects should not contain alternative vocabularies for the same domain that reflect distinct models.
图2.3. U双语语言是在行话的交汇中培养起来的。
Figure 2.3. UBIQUITOUS LANGUAGE is cultivated in the intersection of jargons.
有了统一语言,开发人员之间的对话、领域专家之间的讨论以及代码本身的表达都基于同一种语言,源自共享的领域模型。
With a UBIQUITOUS LANGUAGE, conversations among developers, discussions among domain experts, and expressions in the code itself are all based on the same language, derived from a shared domain model.
每当我在会议上讨论软件设计时,我几乎不能不在白板或画板上画画。我画的大部分是 UML 图,主要是类图或对象交互图。
Whenever I’m in a meeting discussing a software design, I can hardly function without drawing on a whiteboard or sketchpad. A good part of what I draw is UML diagrams, mostly class diagrams or object-interactions.
有些人天生就喜欢视觉,图表可以帮助人们掌握某些类型的信息。UML 图非常善于传达对象之间的关系,并且它们在显示交互方面也不错。但它们并没有传达这些对象的概念定义。在会议上,我会在画图时用言语充实这些含义,或者在与其他参与者的对话中浮现出来。
Some people are naturally visual, and diagrams help people grasp certain kinds of information. UML diagrams are pretty good at communicating relationships between objects, and they are fair at showing interactions. But they do not convey the conceptual definitions of those objects. In a meeting, I would flesh out those meanings in speech as I sketched the diagram, or they would emerge in a dialog with other participants.
简单、非正式的 UML 图可以成为讨论的支柱。绘制一个与当前问题相关的三到五个对象的图,这样每个人都可以集中注意力。每个人都会对对象之间的关系以及对象的名称有一个共同的看法。借助此帮助,口头讨论会更加有效。随着人们尝试不同的思想实验,图表可以发生变化,而草图将具有口头语言的一些流畅性,这是讨论的真正组成部分。毕竟,UML 代表统一建模语言。
Simple, informal UML diagrams can anchor a discussion. Sketch a diagram of three to five objects central to the issue at hand, and everyone can stay focused. Everyone will share a view of the relationships between the objects and, significantly, the objects’ names. The spoken discussion can be more effective with this aid. A diagram can be changed as people try different thought experiments, and the sketch will take on some of the fluidity of spoken words, a true part of the discussion. After all, UML stands for Unified Modeling Language.
当人们觉得必须通过 UML 传达整个模型或设计时,麻烦就来了。许多对象模型图过于完整,同时又遗漏了太多内容。它们过于完整是因为人们觉得必须将所有要编码的对象放入建模工具中。有了这么多细节,就没有人能只见树木不见森林了。
The trouble comes when people feel compelled to convey the whole model or design through UML. A lot of object model diagrams are too complete and, simultaneously, leave too much out. They are too complete because people feel they have to put all the objects that they are going to code into a modeling tool. With all that detail, no one can see the forest for the trees.
然而,尽管有这么多细节,属性和关系只是对象模型的一半。这些对象的行为和对它们的约束并不是那么容易说明的。对象交互图可以说明设计中的一些棘手热点,但大部分交互不能以这种方式显示。创建图表和阅读图表的工作量实在太大。而且交互图仍然只能暗示模型背后的目的。为了包含约束和断言,UML 只能使用文本,将其放在小括号中,插入到图中。
Yet in spite of all that detail, the attributes and relationships are only half the story of an object model. The behavior of those objects and the constraints on them are not so easily illustrated. Object interaction diagrams can illustrate some tricky hotspots in the design, but the bulk of the interactions can’t be shown that way. It is just too much work, both to create the diagrams and to read them. And an interaction diagram can still only imply the purpose behind the model. To include constraints and assertions, UML falls back on text, placed in little brackets, inserted into the diagram.
对象的行为职责可以通过操作名称来暗示,也可以通过对象交互(或序列)图来隐式地展示,但无法表述。因此,这项任务落到了补充文本或对话中。换句话说,UML 图无法传达模型的两个最重要的方面:它所代表的概念的含义以及对象要做什么。不过,这不必困扰我们,因为谨慎使用英语(或西班牙语,或其他语言)可以很好地完成这一角色。
The behavioral responsibilities of an object can be hinted at through operation names, and they can be implicitly demonstrated with object interaction (or sequence) diagrams, but they cannot be stated. So, this task falls to supplemental text or conversation. In other words, a UML diagram cannot convey two of the most important aspects of a model: the meaning of the concepts it represents, and what the objects are meant to do. This needn’t trouble us, though, because careful use of English (or Spanish, or whatever) can fill this role pretty well.
UML 也不是一种令人满意的编程语言。我见过的所有使用建模工具的代码生成功能的尝试都适得其反。如果您受到 UML 功能的限制,您经常不得不忽略模型的最关键部分,因为它是一些不适合用框图表示的规则。当然,代码生成器不能使用这些文本注释。如果您确实使用某种技术,允许使用类似 UML 的图表语言编写可执行程序,那么 UML 图就会被简化为仅仅是查看程序本身的另一种方式,而“模型”的真正含义就会丢失。如果您使用 UML 作为实现语言,您仍然需要其他方法来传达整洁的模型。
Nor is UML a very satisfying programming language. Every attempt I’ve seen to use the code-generation capabilities of the modeling tools has been counterproductive. If you are constrained by the capabilities of UML, you will often have to leave out the most crucial part of the model because it is some rule that doesn’t fit into a box-and-line diagram. And, of course, a code generator cannot make use of those textual annotations. If you do use some technology that allows executable programs to be written in a UML-like diagramming language, then the UML diagram is reduced to merely another way to view the program itself, and the very meaning of “model” is lost. If you use UML as your implementation language, you will still need other means of communicating the uncluttered model.
图表是一种交流和解释的手段,它们有助于集思广益。如果图表尽可能简洁,它们可以更好地达到这些目的。整个对象模型的综合图表无法传达或解释信息;它们让读者被细节淹没,而且缺乏意义。这使我们远离包罗万象的对象模型图,甚至远离包罗万象的 UML 数据库存储库。它引导我们走向简化的概念上重要的对象模型部分,这些部分对于理解设计至关重要。本书中的图表是我在项目中使用的典型图表。它们简化、解释,甚至在澄清要点时融入了一些非标准符号。它们显示了设计约束,但它们并不是每个细节的设计规范。它们代表了想法的骨架。
Diagrams are a means of communication and explanation, and they facilitate brainstorming. They serve these ends best if they are minimal. Comprehensive diagrams of the entire object model fail to communicate or explain; they overwhelm the reader with detail and they lack meaning. This leads us away from the all-encompassing object model diagram, or even the all-encompassing database repository of UML. It leads us toward simplified diagrams of conceptually important parts of the object model that are essential to understanding the design. The diagrams in this book are typical of those I use on projects. They simplify, they explain, and they even incorporate a bit of nonstandard notation when it clarifies their point. They show design constraints, but they are not design specifications in every detail. They represent the skeletons of ideas.
设计的关键细节都体现在代码中。编写良好的实现应该是透明的,可以揭示其底层模型。(确保这一点是下一章以及本书其余大部分内容的主题。)补充图表和文档可以引导人们的注意力到中心点。自然语言讨论可以填补含义的细微差别。这就是为什么我更喜欢从典型的 UML 图处理事物的方式中彻底颠覆事物。我不会写一个用文本注释的图表,而是写一个用选择性和简化的图表说明的文本文档。
The vital detail about the design is captured in the code. A well-written implementation should be transparent, revealing the model underlying it. (Making sure that this happens is the subject of the next chapter and much of the rest of this book.) Supplemental diagrams and documents can guide people’s attention to the central points. Natural language discussion can fill in the nuances of meaning. This is why I prefer to turn things inside out from the way a typical UML diagram handles them. Rather than a diagram annotated with text, I write a text document illustrated with selective and simplified diagrams.
永远记住,模型不是图表。图表的目的是帮助沟通和解释模型。代码可以作为设计细节的存储库。编写良好的 Java 在其表达方式上与 UML 一样富有表现力。精心选择和构建的图表可以集中注意力并帮助导航,只要它们没有被完全表示模型或设计的冲动所掩盖。
Always remember that the model is not the diagram. The diagram’s purpose is to help communicate and explain the model. The code can serve as a repository of the details of the design. Well-written Java is as expressive as UML in its way. Carefully selected and constructed diagrams can serve to focus attention and aid navigation if they are not obscured by a compulsion to represent the model or design completely.
口头交流为代码的严谨性和细节性增添了意义。尽管交流对于将每个人与模型联系起来至关重要,但任何规模的团队都可能需要一些书面文档的稳定性和可共享性。但编写真正有助于团队开发出优质软件的书面文档是一项挑战。
Spoken communication supplements the code’s rigor and detail with meaning. But although talking is critical to connecting everyone to the model, a group of any size will probably need the stability and share-ability of some written documents. But making written documents that actually help the team produce good software is a challenge.
一旦文档形成了持久的形式,它通常会与项目流程失去联系。它会随着代码的演进或项目语言的演进而被遗忘。
Once a document takes on a persistent form, it often loses its connection with the flow of the project. It is left behind by the evolution of the code, or by the evolution of the language of the project.
很多方法都可以奏效。本书第四部分稍后会推荐一些特定的文档,以满足特定需求,但我不会试图规定项目应该使用哪些文档。相反,我将提供两条评估文档的一般准则。
Many approaches can work. A few specific documents will be suggested much later, in Part IV of this book, which address particular needs, but I make no attempt to prescribe a set of documents a project should use. Instead, I will offer two general guidelines for evaluating a document.
每个敏捷过程都有自己的文档哲学。极限编程提倡完全不使用额外的设计文档,并且让代码自己说话。运行代码不会像其他文档那样撒谎。运行代码的行为是明确的。
Each Agile process has its own philosophy about documents. Extreme Programming advocates using no extra design documents at all and letting the code speak for itself. Running code doesn’t lie, as any other document might. The behavior of running code is unambiguous.
极限编程专注于程序的活动元素和可执行测试。即使添加到代码中的注释也不会影响程序行为,因此它们始终与活动代码及其驱动模型不同步。外部文档和图表不会影响程序的行为,因此它们不同步。另一方面,口头交流和白板上的短暂图表不会造成混乱。这种对代码作为通信媒介的依赖促使开发人员保持代码的干净和透明。
Extreme Programming concentrates exclusively on the active elements of a program and executable tests. Even comments added to the code do not affect program behavior, so they always fall out of sync with the active code and its driving model. External documents and diagrams do not affect the behavior of the program, so they fall out of sync. On the other hand, spoken communication and ephemeral diagrams on whiteboards do not linger to create confusion. This dependence on the code as communication medium motivates developers to keep the code clean and transparent.
但是,代码作为设计文档确实有其局限性。它可能让读者被细节淹没。尽管它的行为是明确的,但这并不意味着它是显而易见的。行为背后的含义可能很难传达。换句话说,仅通过代码进行记录与使用全面的 UML 图存在一些相同的基本问题。当然,团队内部的大量口头交流提供了有关代码的背景和指导,但它是短暂的和局部的。而且开发人员并不是唯一需要了解模型的人。
But code as a design document does have its limits. It can overwhelm the reader with detail. Although its behavior is unambiguous, that doesn’t mean it is obvious. And the meaning behind a behavior can be hard to convey. In other words, documenting exclusively through code has some of the same basic problems as using comprehensive UML diagrams. Of course, massive spoken communication within the team gives context and guidance around the code, but it is ephemeral and localized. And developers are not the only people who need to understand the model.
文档不应该尝试做代码已经能做好的事情。代码已经提供了细节。它是程序行为的精确规范。
A document shouldn’t try to do what the code already does well. The code already supplies the detail. It is an exact specification of program behavior.
其他文档需要阐明含义,深入了解大型结构,并将注意力集中在核心元素上。当编程语言不支持概念的直接实现时,文档可以阐明设计意图。书面文档应该补充代码和谈话。
Other documents need to illuminate meaning, to give insight into large-scale structures, and to focus attention on core elements. Documents can clarify design intent when the programming language does not support a straightforward implementation of a concept. Written documents should complement the code and the talking.
当我以书面形式记录模型时,我会绘制模型中精心挑选的小子集,并用文本包围它们。我用文字定义类及其职责,并将它们置于只有自然语言才能表达的意义中。但该图显示了在将概念形式化并精简为对象模型时所做的一些选择。这些图可以有些随意——甚至是手绘的。除了节省劳动力之外,手绘图表的优点是给人一种随意和临时的感觉。这些都是很好的交流方式,因为它们通常适用于我们的模型想法。
When I document a model in writing, I diagram small, carefully selected subsets of the model and surround them with text. I define the classes and their responsibilities in words and frame them in a context of meaning as only a natural language can. But the diagram shows some of the choices that have been made in formalizing and paring down the concepts into an object model. These diagrams can be somewhat casual—even hand-drawn. In addition to saving labor, hand-drawn diagrams have the advantage of feeling casual and temporary. These are good things to communicate because they are generally true of our model ideas.
设计文档的最大价值在于解释模型的概念、帮助理解代码的细节,或许还能对模型的预期使用风格提供一些见解。根据团队的理念,整个设计文档可能只是一组贴在墙上的草图,也可能非常充实。
The greatest value of a design document is to explain the concepts of the model, help in navigating the detail of the code, and perhaps give some insight into the model’s intended style of use. Depending on the philosophy of the team, the whole design document could be as simple as a set of sketches posted on the walls, or it could be substantial.
文档必须涉及项目活动。判断这一点的最简单方法是观察文档与UBIQUITOUS LANGUAGE的交互。文档是用项目人员(现在)使用的语言编写的吗?它是用代码中嵌入的语言编写的吗?
A document must be involved in project activities. The easiest way to judge this is to observe the document’s interaction with the UBIQUITOUS LANGUAGE. Is the document written in the language people speak on the project (now)? Is it written in the language embedded in the code?
聆听UBIQUITOUS LANGUAGE及其变化。如果设计文档中解释的术语没有在对话和代码中出现,则文档没有实现其目的。也许文档太大或太复杂。也许它没有关注足够重要的主题。人们要么不读它,要么觉得它没有吸引力。如果它对 UBIQUITOUS LANGUAGE 没有影响,那就出了问题。
Listen to the UBIQUITOUS LANGUAGE and how it is changing. If the terms explained in a design document don’t start showing up in conversations and code, the document is not fulfilling its purpose. Maybe the document is too big or complicated. Maybe it is not focused on a sufficiently important topic. People are either not reading it or not finding it compelling. If it is having no impact on the UBIQUITOUS LANGUAGE, something is wrong.
相反,您可能会听到UBIQUITOUS LANGUAGE自然发生变化,而文档被遗弃。显然,该文档似乎与人们无关,或者似乎不够重要,不值得更新。它可以安全地归档为历史记录,但如果保持活跃,则可能会造成混乱并损害项目。如果文档没有发挥重要作用,仅凭意志和纪律使其保持最新状态就是浪费精力。
Conversely, you may hear the UBIQUITOUS LANGUAGE changing naturally while a document is being left behind. Evidently the document does not seem relevant to people or does not seem important enough to update. It could safely be archived as history, but left active it could create confusion and hurt the project. And if a document isn’t playing an important role, keeping it up to date through sheer will and discipline wastes effort.
UBIQUITOUS LANGUAGE使其他文档(例如需求规范)更加简洁,歧义更少。随着领域模型逐渐反映出最相关的业务知识,应用程序需求成为该模型中的场景,UBIQUITOUS LANGUAGE可用于以与模型驱动设计直接相关的术语来描述此类场景(参见第 3 章)。因此,规范可以写得更简单,因为它们不必传达模型背后的业务知识。
The UBIQUITOUS LANGUAGE allows other documents, such as requirements specifications, to be more concise and less ambiguous. As the domain model comes to reflect the most relevant knowledge of the business, application requirements become scenarios within that model, and the UBIQUITOUS LANGUAGE can be used to describe such a scenario in terms that directly connect to the MODEL-DRIVEN DESIGN (see Chapter 3). As a result, specifications can be written more simply, because they do not have to convey the business knowledge that lies behind the model.
通过保持文档的简洁性,并使其专注于补充代码和对话,文档可以与项目保持联系。让UBIQUITOUS LANGUAGE及其演变成为您的指南,选择可以融入项目活动的文档。
By keeping documents minimal and focusing them on complementing code and conversation, documents can stay connected to the project. Let the UBIQUITOUS LANGUAGE and its evolution be your guide to choosing documents that live and get woven into the project’s activity.
现在让我们来研究一下 XP 社区和其他一些社区的选择,他们几乎完全依赖可执行代码及其测试。本书的大部分内容讨论了通过模型驱动设计使代码传达含义的方法(参见第 3 章)。编写良好的代码可以非常具有交流性,但它所传达的信息并不能保证准确无误。哦,一段代码导致的行为的现实是不可避免的。但是,与方法的内部结构相比,方法名称可能会含糊不清、误导或过时。测试中的断言是严格的,但变量名和代码组织所讲述的故事却不是。良好的编程风格使这种联系尽可能直接,但它仍然是一种自律的练习。编写不仅做正确的事情而且表达正确的事情的代码需要一丝不苟。
Now let’s examine the choice of the XP community and some others, to rely almost exclusively on the executable code and its tests. Much of this book discusses ways to make the code convey meaning through a MODEL-DRIVEN DESIGN (see Chapter 3). Well-written code can be very communicative, but the message it communicates is not guaranteed to be accurate. Oh, the reality of the behavior caused by a section of code is inescapable. But a method name can be ambiguous, misleading, or out of date compared to the internals of the method. The assertions in a test are rigorous, but the story told by variable names and the organization of the code is not. Good programming style keeps this connection as direct as possible, but it is still an exercise in self-discipline. It takes fastidiousness to write code that doesn’t just do the right thing but also says the right thing.
消除这些差异是声明式设计(第 10 章讨论)等方法的主要卖点,在声明式设计中,程序元素的目的声明决定了其在程序中的实际行为。从 UML 生成程序的动力部分受此驱动,尽管到目前为止效果并不好。
Elimination of those discrepancies is a major selling point of approaches such as declarative design (discussed in Chapter 10), in which a statement of the purpose of a program element determines its actual behavior in the program. The drive to generate programs from UML is partly motivated by this, though it generally hasn’t worked out well so far.
尽管如此,尽管代码也可能产生误导,但它比其他文档更贴近现实。使用当前标准技术来协调代码的行为、意图和信息需要纪律和一定的设计思维方式(第三部分将详细讨论)。为了有效沟通,代码必须基于编写需求时使用的相同语言——开发人员之间以及与领域专家交流时使用的相同语言。
Still, while even code can mislead, it is closer to the ground than other documents. Aligning the behavior, intent, and message of code using current standard technology requires discipline and a certain way of thinking about design (discussed at length in Part III). To communicate effectively, the code must be based on the same language used to write the requirements—the same language that the developers speak with each other and with domain experts.
本书的重点是,实施、设计和团队沟通应采用同一个模型。为这些不同的目的而采用不同的模型会带来危险。
The thrust of this book is that one model should underlie implementation, design, and team communication. Having separate models for these separate purposes poses a hazard.
模型还可以作为教学辅助工具,用于教授领域知识。驱动设计的模型是领域的一种视角,但它可能有助于学习其他视角,这些视角仅用作教学工具,用于传达领域的一般知识。为此,人们可以使用图片或文字来传达与软件设计无关的其他类型的模型。
Models can also be valuable as education aids to teach about the domain. The model that drives the design is one view of the domain, but it may aid learning to have other views, used only as educational tools, to communicate general knowledge of the domain. For this purpose, people can use pictures or words that convey other kinds of models unrelated to software design.
需要其他模型的一个特殊原因是范围。驱动软件开发过程的技术模型必须严格缩减到必要的最低限度才能发挥其功能。解释模型可以包括领域的各个方面,这些方面提供了阐明范围更窄的模型的背景。
One particular reason that other models are needed is scope. The technical model that drives the software development process must be strictly pared down to the necessary minimum to fulfill its functions. An explanatory model can include aspects of the domain that provide context that clarifies the more narrowly scoped model.
解释模型提供了创建针对特定主题的更具沟通风格的自由。领域专家使用的视觉隐喻通常可以提供更清晰的解释,教育开发人员并协调专家。解释模型还以不同的方式呈现领域,多种多样的解释有助于人们学习。
Explanatory models offer the freedom to create much more communicative styles tailored to a particular topic. Visual metaphors used by the domain experts in a field often present clearer explanations, educating developers and harmonizing experts. Explanatory models also present the domain in a way that is simply different, and multiple, diverse explanations help people learn.
解释模型没有必要是对象模型,通常最好不是对象模型。实际上,避免在这些模型中使用 UML 是有帮助的,这样可以避免任何与软件设计相对应的错误印象。尽管解释模型和驱动设计的模型确实经常对应,但相似之处很少是完全相同的。为了避免混淆,每个人都必须意识到这种区别。
There is no need for explanatory models to be object models, and it is generally best if they are not. It is actually helpful to avoid UML in these models, to avoid any false impression of correspondence with the software design. Even though the explanatory model and the model that drives design do often correspond, the similarities will seldom be exact. To avoid confusion, everyone must be conscious of the distinction.
考虑一个为航运公司跟踪货物的应用程序。该模型包括港口运营和船舶航行如何组合成货物运营计划(“路线”)的详细视图。但对于初学者来说,类图可能不太具启发性。
Consider an application that tracks cargos for a shipping company. The model includes a detailed view of how port operations and vessel voyages are assembled into an operational plan for a cargo (a “route”). But to the uninitiated, a class diagram may not be very illuminating.
图 2.4. 航运路线的类图
Figure 2.4. A class diagram for a shipping route
在这种情况下,解释模型可以帮助团队成员理解类图的实际含义。以下是看待相同概念的另一种方式:
In such a case, an explanatory model can help team members understand what the class diagram actually means. Here is another way of looking at the same concepts:
图 2.5中的每条线表示港口操作(装载或卸载货物)、货物在地面的储存或货物在途中的船上。这与类图没有详细对应,但它强调了领域中的关键点。
Each line in Figure 2.5 represents either a port operation (loading or unloading the cargo), or cargo sitting in storage on the ground, or cargo sitting on a ship en route. This does not correspond in detail with the class diagram, but it reinforces key points from the domain.
图 2.5. 航运路线的解释模型
Figure 2.5. An explanatory model for a shipping route
这种图表,连同其所代表模型的自然语言解释,可以帮助开发人员和领域专家理解更严格的软件模型图。它们结合起来比单独使用任何一种视图更容易理解。
This sort of diagram, along with natural language explanations of the model it represents, can help developers and domain experts alike understand the more rigorous software model diagrams. Together they are easier to understand than either view alone.
走进门,我首先看到的是一张完整的类图,它印在一张大纸上,覆盖了一大面墙。这是我第一天参与这个项目,聪明的人花了几个月的时间仔细研究和开发该领域的详细模型。模型中的典型对象与三四个其他对象有着错综复杂的关联,而这种关联网络几乎没有自然边界。在这方面,分析师们对领域的本质非常了解。
The first thing I saw as I walked through the door was a complete class diagram printed on large sheets of paper that covered a large wall. It was my first day on a project in which smart people had spent months carefully researching and developing a detailed model of the domain. The typical object in the model had intricate associations with three or four other objects, and this web of associations had few natural borders. In this respect, the analysts had been true to the nature of the domain.
尽管这张墙大小的图表令人眼花缭乱,但该模型确实收集了一些知识。经过一段时间的研究,我学到了很多东西(尽管这种学习很难指导——就像随机浏览网页一样)。更让我不安的是,我发现我的研究并没有让我深入了解应用程序的代码和设计。
As overwhelming as the wall-size diagram was, the model did capture some knowledge. After a moderate amount of study, I learned quite a bit (though that learning was hard to direct—much like randomly browsing the Web). I was more troubled to find that my study gave no insight into the application’s code and design.
当开发人员开始实施该应用程序时,他们很快发现,尽管人类分析师可以驾驭这些复杂的关联,但它们无法转化为可存储、可检索的单元,无法以事务完整性的方式进行操作。请注意,该项目使用的是对象数据库,因此开发人员甚至不必面对将对象映射到关系表的挑战。从根本上讲,该模型没有提供实施指南。
When the developers had begun implementing the application, they had quickly discovered that the tangle of associations, although navigable by a human analyst, didn’t translate into storable, retrievable units that could be manipulated with transactional integrity. Mind you, this project was using an object database, so the developers didn’t even have to face the challenges of mapping objects into relational tables. At a fundamental level, the model did not provide a guide to implementation.
由于该模型是“正确的”,是技术分析师和业务专家广泛合作的结果,因此开发人员得出结论:基于概念的对象无法作为其设计的基础。因此,他们着手开发一种临时设计。他们的设计确实使用了一些相同的类名和属性来存储数据,但它并不基于现有的或任何模型。
Because the model was “correct,” the result of extensive collaboration between technical analysts and business experts, the developers reached the conclusion that conceptually based objects could not be the foundation of their design. So they proceeded to develop an ad hoc design. Their design did use a few of the same class names and attributes for data storage, but it was not based on the existing, or any, model.
该项目有一个领域模型,但是,如果不能直接帮助运行软件的开发,那么纸面上的模型还有什么用呢?
The project had a domain model, but what good is a model on paper unless it directly aids the development of running software?
几年后,我看到了完全不同的过程带来的相同结果。这个项目是用 Java 实现的新设计替换现有的 C++ 应用程序。旧应用程序是在没有考虑对象建模的情况下拼凑起来的。旧应用程序的设计(如果有的话)随着一个又一个功能被置于现有代码之上而逐渐积累,没有任何明显的概括或抽象。
A few years later, I saw the same end result come from a completely different process. This project was to replace an existing C++ application with a new design implemented in Java. The old application had been hacked together without any regard for object modeling. The design of the old application, if there was one, had accreted as one capability after another had been laid on top of the existing code, without any noticeable generalization or abstraction.
令人毛骨悚然的是,这两个过程的最终产品非常相似!两者都具有功能,但都臃肿、难以理解,最终无法维护。虽然实现在某些地方具有某种直接性,但你无法通过阅读代码来深入了解系统的目的。这两个过程都没有利用其开发环境中可用的对象范式,除非将其用作奇特的数据结构。
The eerie thing was that the end products of the two processes were very similar! Both had functionality, but were bloated, very hard to understand, and eventually unmaintainable. Though the implementations had, in places, a kind of directness, you couldn’t gain much insight about the purpose of the system by reading the code. Neither process took any advantage of the object paradigm available in their development environment, except as fancy data structures.
模型种类繁多,作用各异,甚至仅限于软件开发项目环境。领域驱动设计要求模型不仅有助于早期分析,而且是设计的基础。这种方法对代码具有一些重要影响。不太明显的是,领域驱动设计需要一种不同的建模方法。...
Models come in many varieties and serve many roles, even those restricted to the context of a software development project. Domain-driven design calls for a model that doesn’t just aid early analysis but is the very foundation of the design. This approach has some important implications for the code. What is less obvious is that domain-driven design requires a different approach to modeling. . . .
星盘用于计算恒星的位置,是天空模型的机械实现。
The astrolabe, used to compute star positions, is a mechanical implementation of a model of the sky.
将代码与底层模型紧密关联可以赋予代码含义并使模型具有相关性。
Tightly relating the code to an underlying model gives the code meaning and makes the model relevant.
那些根本没有领域模型,而只是编写代码来实现一个又一个功能的项目,几乎得不到前两章中讨论的知识整合和沟通优势。复杂的领域会让他们不知所措。
Projects that have no domain model at all, but just write code to fulfill one function after another, gain few of the advantages of knowledge crunching and communication discussed in the previous two chapters. A complex domain will swamp them.
另一方面,许多复杂项目确实尝试了某种领域模型,但它们没有在模型和代码之间保持紧密联系。他们开发的模型,一开始可能作为探索工具很有用,但后来变得越来越不相关,甚至具有误导性。对模型的所有关注都无法保证设计是正确的,因为两者是不同的。
On the other hand, many complex projects do attempt some sort of domain model, but they don’t maintain a tight connection between the model and the code. The model they develop, possibly useful as an exploratory tool at the outset, becomes increasingly irrelevant and even misleading. All the care lavished on the model provides little reassurance that the design is correct, because the two are different.
这种联系可能以多种方式破裂,但分离往往是一种有意识的选择。许多设计方法都提倡一种分析模型,它与设计截然不同,通常由不同的人开发。它之所以被称为分析模型,是因为它是分析业务领域的产物,用于组织其概念,而不考虑它在软件系统中将扮演的角色。分析模型仅作为一种理解工具;混入实施问题被认为会把事情弄得更复杂。后来,设计可能与分析模型只有松散的对应关系。分析模型在创建时并没有考虑到设计问题,因此对于这些需求来说,它很可能是不切实际的。
This connection can break down in many ways, but the detachment is often a conscious choice. Many design methodologies advocate an analysis model, quite distinct from the design and usually developed by different people. It is called an analysis model because it is the product of analyzing the business domain to organize its concepts without any consideration of the part it will play in a software system. An analysis model is meant as a tool for understanding only; mixing in implementation concerns is thought to muddy the waters. Later, a design is created that may have only a loose correspondence to the analysis model. The analysis model is not created with design issues in mind, and therefore it is likely to be quite impractical for those needs.
在这样的分析过程中,一些知识会被消化,但大部分知识会在编码开始时丢失,因为开发人员被迫为设计提出新的抽象。那么,就无法保证分析师获得并嵌入模型中的见解会被保留或重新发现。此时,维护设计和松散连接模型之间的任何映射都是不划算的。
Some knowledge crunching happens during such an analysis, but most of it is lost when coding begins, when the developers are forced to come up with new abstractions for the design. Then there is no guarantee that the insights gained by the analysts and embedded in the model will be retained or rediscovered. At this point, maintaining any mapping between the design and the loosely connected model is not cost-effective.
纯分析模型甚至达不到理解领域的主要目标,因为在设计/实施过程中总会有关键的发现。总是会出现非常具体、意想不到的问题。前期模型会深入研究一些不相关的主题,而忽略一些重要的主题。其他主题将以对应用程序无用的方式呈现。结果是纯分析模型在编码开始后不久就被抛弃,大部分内容必须重新覆盖。但第二次,如果开发人员认为分析是一个单独的过程,建模就会以不太规范的方式进行。如果管理人员认为分析是一个单独的过程,开发团队可能无法充分接触领域专家。
The pure analysis model even falls short of its primary goal of understanding the domain, because crucial discoveries always emerge during the design/implementation effort. Very specific, unanticipated problems always arise. An up-front model will go into depth about some irrelevant subjects, while it overlooks some important subjects. Other subjects will be represented in ways that are not useful to the application. The result is that pure analysis models get abandoned soon after coding starts, and most of the ground has to be covered again. But the second time around, if the developers perceive analysis to be a separate process, modeling happens in a less disciplined way. If the managers perceive analysis to be a separate process, the development team may not be given adequate access to domain experts.
无论原因是什么,在设计基础上缺乏概念的软件充其量只是一种无法解释其行为的执行有用功能的机制。
Whatever the cause, software that lacks a concept at the foundation of its design is, at best, a mechanism that does useful things without explaining its actions.
如果设计或设计中的某个核心部分没有映射到领域模型,那么该模型就没有什么价值,软件的正确性也值得怀疑。同时,模型和设计功能之间的复杂映射很难理解,实际上,随着设计的变化,无法维护。分析和设计之间存在着致命的鸿沟,因此在每一项活动中获得的见解都不会对另一项产生影响。
If the design, or some central part of it, does not map to the domain model, that model is of little value, and the correctness of the software is suspect. At the same time, complex mappings between models and design functions are difficult to understand and, in practice, impossible to maintain as the design changes. A deadly divide opens between analysis and design so that insight gained in each of those activities does not feed into the other.
分析必须以易于理解、富有表现力的方式捕捉领域中的基本概念。设计必须指定一组可以用编程构建的组件项目中使用的工具将在目标部署环境中有效运行,并能正确解决应用程序所带来的问题。
An analysis must capture fundamental concepts from the domain in a comprehensible, expressive way. The design has to specify a set of components that can be constructed with the programming tools in use on the project that will perform efficiently in the target deployment environment and will correctly solve the problems posed for the application.
模型驱动设计摒弃了分析模型和设计的二分法,寻找一个可以同时满足两个目的的单一模型。撇开纯粹的技术问题不谈,设计中的每个对象都扮演着模型中描述的概念角色。这要求我们对所选模型的要求更高,因为它必须满足两个完全不同的目标。
MODEL-DRIVEN DESIGN discards the dichotomy of analysis model and design to search out a single model that serves both purposes. Setting aside purely technical issues, each object in the design plays a conceptual role described in the model. This requires us to be more demanding of the chosen model, since it must fulfill two quite different objectives.
抽象领域的方法总是多种多样的,解决应用问题的设计也总是多种多样的。这就是将模型和设计结合起来的实用性所在。这种结合绝不能以削弱分析为代价,而这种削弱分析会因技术考虑而受到致命损害。我们也不能接受笨拙的设计,这种设计反映了领域思想,但却回避了软件设计原则。这种方法要求模型既能很好地进行分析,又能很好地进行设计。当一个模型似乎不切实际时,我们必须寻找一个新的模型。当一个模型不能忠实地表达领域的关键概念时,我们必须寻找一个新的模型。然后,建模和设计过程就变成了一个单一的迭代循环。
There are always many ways of abstracting a domain, and there are always many designs that can solve an application problem. This is what makes it practical to bind the model and design. This binding mustn’t come at the cost of a weakened analysis, fatally compromised by technical considerations. Nor can we accept clumsy designs, reflecting domain ideas but eschewing software design principles. This approach demands a model that works well as both analysis and design. When a model doesn’t seem to be practical for implementation, we must search for a new one. When a model doesn’t faithfully express the key concepts of the domain, we must search for a new one. The modeling and design process then becomes a single iterative loop.
将领域模型与设计紧密关联起来的必要性为从众多可能的模型中选择更有用的模型增加了一个标准。这需要深思熟虑,通常需要多次迭代和大量重构,但它使模型具有相关性。
The imperative to relate the domain model closely to the design adds one more criterion for choosing the more useful models out of the universe of possible models. It calls for hard thinking and usually takes multiple iterations and a lot of refactoring, but it makes the model relevant.
所以:
Therefore:
设计软件系统的一部分,以非常直观的方式反映领域模型,以便映射显而易见。重新审视模型并对其进行修改,使其在软件中更自然地实现,即使您试图使其反映对领域的更深入洞察。除了支持强大的UBIQUITOUS LANGUAGE之外,还需要一个能够很好地满足两个目的的单一模型。
Design a portion of the software system to reflect the domain model in a very literal way, so that mapping is obvious. Revisit the model and modify it to be implemented more naturally in software, even as you seek to make it reflect deeper insight into the domain. Demand a single model that serves both purposes well, in addition to supporting a robust UBIQUITOUS LANGUAGE.
从模型中得出设计中使用的术语和基本职责分配。代码成为模型的表达,因此对代码的更改可能是对模型的更改。其影响必须相应地波及项目的其他活动。
Draw from the model the terminology used in the design and the basic assignment of responsibilities. The code becomes an expression of the model, so a change to the code may be a change to the model. Its effect must ripple through the rest of the project’s activities accordingly.
为了将实现严格地绑定到模型,通常需要支持建模范式的软件开发工具和语言,例如面向对象编程。
To tie the implementation slavishly to a model usually requires software development tools and languages that support a modeling paradigm, such as object-oriented programming.
有时,不同的子系统会有不同的模型(参见第 14 章),但在整个开发工作的各个方面(从代码到需求分析),只有一个模型应该适用于系统的特定部分。
Sometimes there will be different models for different subsystems (see Chapter 14), but only one model should apply to a particular part of the system, throughout all aspects of the development effort, from the code to requirements analysis.
单一模型降低了出错的几率,因为设计现在是经过深思熟虑的模型的直接产物。设计,甚至代码本身,都具有模型的可沟通性。
The single model reduces the chances of error, because the design is now a direct outgrowth of the carefully considered model. The design, and even the code itself, has the communicativeness of a model.
开发一个能够捕捉问题并提供实用设计的单一模型说起来容易做起来难。您不能随便采用任何模型并将其变成可行的设计。必须精心设计模型才能实现实际实施。必须采用设计和实施技术,使代码能够有效地表达模型(参见第 II 部分)。知识处理者探索模型选项并将其细化为实用的软件元素。开发成为一个迭代过程,将模型、设计和代码作为单一活动进行细化(参见第 III 部分)。
Developing a single model that captures the problem and provides a practical design is easier said than done. You can’t just take any model and turn it into a workable design. The model has to be carefully crafted to make for a practical implementation. Design and implementation techniques have to be employed that allow code to express a model effectively (see Part II). Knowledge crunchers explore model options and refine them into practical software elements. Development becomes an iterative process of refining the model, the design, and the code as a single activity (see Part III).
要使模型驱动设计取得成功,对应关系必须是字面意义上的、精确的,不能超出人为误差的范围。要使模型和设计之间有如此密切的对应关系,在由软件工具支持的建模范式内工作几乎是必不可少的,这些软件工具允许您创建与模型中的概念直接类似的模型。
To make a MODEL-DRIVEN DESIGN pay off, the correspondence must be literal, exact within bounds of human error. To make such a close correspondence of model and design possible, it is almost essential to work within a modeling paradigm supported by software tools that allow you to create direct analogs to the concepts in the model.
图 3.1
Figure 3.1
面向对象编程之所以强大,是因为它基于建模范例,并且提供了模型构造的实现。就程序员而言,对象确实存在于内存中,它们与其他对象有关联,它们被组织成类,并且它们提供通过消息传递可用的行为。尽管许多开发人员仅从应用对象的技术功能来组织程序代码中受益,但对象设计的真正突破是在代码表达模型概念时。Java 和许多其他工具允许创建直接类似于概念对象模型的对象和关系。
Object-oriented programming is powerful because it is based on a modeling paradigm, and it provides implementations of the model constructs. As far as the programmer is concerned, objects really exist in memory, they have associations with other objects, they are organized into classes, and they provide behavior available by messaging. Although many developers benefit from just applying the technical capabilities of objects to organize program code, the real breakthrough of object design comes when the code expresses the concepts of a model. Java and many other tools allow the creation of objects and relationships directly analogous to conceptual object models.
尽管 Prolog 语言从未达到面向对象语言的广泛使用水平,但它却非常适合模型驱动设计。在这种情况下,范式就是逻辑,模型就是一组逻辑规则和它们所依据的事实。
Although it has never reached the mass usage that object-oriented languages have, the Prolog language is a natural fit for MODEL-DRIVEN DESIGN. In this case, the paradigm is logic, and the model is a set of logical rules and facts they operate on.
模型驱动设计在使用 C 等语言时适用性有限,因为没有与纯过程语言相对应的建模范式。这些语言是过程性的,因为程序员告诉计算机要遵循一系列步骤。虽然程序员可能正在考虑领域的概念,但程序本身是一系列数据的技术操作。结果可能有用,但程序并没有捕捉到太多含义。过程语言通常支持开始对应于更自然的领域概念的复杂数据类型,但这些复杂类型只是有组织的数据,它们不会捕捉领域的活动方面。结果是,用过程语言编写的软件具有基于预期执行路径链接在一起的复杂功能,而不是通过领域模型中的概念连接。
MODEL-DRIVEN DESIGN has limited applicability using languages such as C, because there is no modeling paradigm that corresponds to a purely procedural language. Those languages are procedural in the sense that the programmer tells the computer a series of steps to follow. Although the programmer may be thinking about the concepts of the domain, the program itself is a series of technical manipulations of data. The result may be useful, but the program doesn’t capture much of the meaning. Procedural languages often support complex data types that begin to correspond to more natural conceptions of the domain, but these complex types are only organized data, and they don’t capture the active aspects of the domain. The result is that software written in procedural languages has complicated functions linked together based on anticipated paths of execution, rather than by conceptual connections in the domain model.
在我听说过面向对象编程之前,我编写了FORTRAN程序来解决数学模型,而这正是FORTRAN擅长的领域。数学函数是这种模型的主要概念组成部分,可以用FORTRAN清晰地表达。即便如此,也没有办法捕捉函数之外的更高层次的含义。大多数非数学领域不适合在程序设计中使用模型驱动设计语言,因为域没有被概念化为数学函数或程序中的步骤。
Before I ever heard of object-oriented programming, I wrote FORTRAN programs to solve mathematical models, which is just the sort of domain in which FORTRAN excels. Mathematical functions are the main conceptual component of such a model and can be cleanly expressed in FORTRAN. Even so, there is no way to capture higher level meaning beyond the functions. Most non-mathematical domains don’t lend themselves to MODEL-DRIVEN DESIGN in procedural languages because the domains are not conceptualized as math functions or as steps in a procedure.
面向对象设计是当前主导大多数雄心勃勃的项目的范式,也是本书主要采用的方法。
Object-oriented design, the paradigm that currently dominates the majority of ambitious projects, is the approach used primarily in this book.
如第 1 章所述,印刷电路板 (PCB) 可视为连接各种元件引脚的电导体(称为网络)的集合。通常有成千上万个网络。一种称为 PCB 布局工具的特殊软件可以为所有网络找到物理排列,使它们不会相互交叉或干扰。它通过优化它们的路径来实现这一点,同时满足人类设计师设置的大量限制,这些限制限制了它们的布局方式。尽管 PCB 布局工具非常复杂,但它们仍然存在一些缺点。
As discussed in Chapter 1, a printed circuit board (PCB) can be viewed as a collection of electrical conductors (called nets) connecting the pins of various components. There are often tens of thousands of nets. Special software, called a PCB layout tool, finds a physical arrangement for all the nets so that they don’t cross or interfere with each other. It does this by optimizing their paths while satisfying an enormous number of constraints placed by the human designers that restrict the way they can be laid out. Although PCB layout tools are very sophisticated, they still have some shortcomings.
一个问题是,这数千个网络中的每一个都有自己的一套布局规则。 PCB 工程师认为许多网络属于自然分组,应该共享相同的规则。 例如,一些网络形成总线。
One problem is that each of these thousands of nets has its own set of layout rules. PCB engineers see many nets as belonging to natural groupings that should share the same rules. For example, some nets form buses.
图 3.2. 总线和网络的说明图
Figure 3.2. An explanatory diagram of buses and nets
通过将网络集中到总线中,一次可能为 8 个、16 个或 256 个,工程师可以将工作量缩减到更易于管理的规模,从而提高生产率并减少错误。问题是,布局工具没有总线的概念。必须将规则分配给数以万计的网络,每次一个网络。
By lumping nets into a bus, perhaps 8 or 16 or 256 at a time, the engineer cuts the job down to a more manageable size, improving productivity and reducing errors. The trouble is, the layout tool has no such concept as a bus. Rules have to be assigned to tens of thousands of nets, one net at a time.
绝望的工程师们通过编写脚本来解决布局工具中的这个限制,这些脚本可以解析布局工具的数据文件并将规则直接插入到文件中,然后一次将它们应用于整个总线。
Desperate engineers worked around this limitation in the layout tool by writing scripts that parse the layout tool’s data files and insert rules directly into the file, applying them to an entire bus at a time.
布局工具将每个电路连接存储在网络列表文件中,该文件看起来像这样:
The layout tool stores each circuit connection in a net list file, which looks something like this:
网络名称 组件.引脚
-------- -------------
Xyz0 A.0, B.0
Xyz1 A.1, B.1
Xyz2 A.2, B.2
. . .
Net Name Component.Pin
-------- -------------
Xyz0 A.0, B.0
Xyz1 A.1, B.1
Xyz2 A.2, B.2
. . .
它以如下文件格式存储布局规则:
It stores the layout rules in a file format something like this:
网络名称规则类型参数
-------- --------- ----------
Xyz1 min_linewidth 5
Xyz1 max_delay 15
Xyz2 min_linewidth 5
Xyz2 max_delay 15
. . .
Net Name Rule Type Parameters
-------- --------- ----------
Xyz1 min_linewidth 5
Xyz1 max_delay 15
Xyz2 min_linewidth 5
Xyz2 max_delay 15
. . .
工程师们谨慎地使用网络命名约定,以便按字母顺序对数据文件进行排序,将总线的网络放在一个排序文件中。然后他们的脚本可以解析文件并根据总线修改每个网络。解析、操作和写入文件的实际代码太冗长和不透明,无法用于此示例,因此我仅列出过程中的步骤。
The engineers carefully use a naming convention for the nets so that an alphabetical sort of the data file will place the nets of a bus together in a sorted file. Then their script can parse the file and modify each net based on its bus. Actual code to parse, manipulate, and write the files is just too verbose and opaque to serve this example, so I’ll just list the steps in the procedure.
1. 按网络名称对网络列表文件进行排序。2
. 读取文件中的每一行,寻找第一个以总线名称模式开头的行。3
. 对于具有匹配名称的每一行,解析行以获取网络名称。4
. 将带有规则文本的网络名称附加到规则文件中。5
. 从 3 开始重复,直到行左侧不再与总线名称匹配。
1. Sort net list file by net name.
2. Read each line in file, seeking first one that starts with bus name pattern.
3. For each line with matching name, parse line to get net name.
4. Append net name with rule text to rules file.
5. Repeat from 3 until left of line no longer matches bus name.
So the input of a bus rule such as this:
总线名称规则类型参数
-------- --------- ----------
Xyz max_vias 3
Bus Name Rule Type Parameters
-------- --------- ----------
Xyz max_vias 3
将导致向文件添加如下网络规则:
would result in adding net rules to the file like these:
网络名称规则类型参数
-------- --------- ----------
. . .
Xyz0 max_vias 3
Xyz1 max_vias 3
Xyz2 max_vias 3
. . .
Net Name Rule Type Parameters
-------- --------- ----------
. . .
Xyz0 max_vias 3
Xyz1 max_vias 3
Xyz2 max_vias 3
. . .
我想,第一个编写此类脚本的人只有这个简单的需求,如果这是唯一的要求,那么这样的脚本就很有意义了。但实际上,现在有几十个脚本。当然,它们可以重构以共享排序和字符串匹配函数,如果语言支持函数调用来封装细节,那么脚本可以开始读起来几乎像上面的摘要步骤。但它们仍然只是文件操作。不同的文件格式(有几种)需要从头开始,即使对总线进行分组并对其应用规则的概念是相同的。如果您想要更丰富的功能或交互性,则必须付出每一点代价。
I imagine that the person who first wrote such a script had only this simple need, and if this were the only requirement, a script like this would make a lot of sense. But in practice, there are now dozens of scripts. They could, of course, be refactored to share sorting and string matching functions, and if the language supported function calls to encapsulate the details, the scripts could begin to read almost like the summary steps above. But still, they are just file manipulations. A different file format (and there are several) would require starting from scratch, even though the concept of grouping buses and applying rules to them is the same. If you wanted richer functionality or interactivity, you would have to pay for every inch.
脚本编写者试图用“总线”的概念来补充该工具的域模型。他们的实现通过排序和字符串匹配推断出总线的存在,但并没有明确处理这个概念。
What the script writers were trying to do was to supplement the tool’s domain model with the concept of “bus.” Their implementation infers the bus’s existence through sorts and string matches, but it does not explicitly deal with the concept.
前面的讨论已经描述了领域专家用来思考问题的概念。现在我们需要将这些概念明确地组织成一个可以作为软件基础的模型。
The preceding discussion has already described the concepts the domain experts use to think about their problems. Now we need to organize those concepts explicitly into a model we can base software on.
图 3.3. 面向高效分配布局规则的类图
Figure 3.3. A class diagram oriented toward efficient assignment of layout rules
With these objects implemented in an object-oriented language, the core functionality becomes almost trivial.
该方法可以在Abstract Net 上实现。Net上assignRule()的方法采用其自己的规则及其Bus的规则。assignedRules()
The assignRule() method can be implemented on Abstract Net. The assignedRules() method on Net takes its own rules and its Bus’s rules.
抽象类 AbstractNet {
私有设置规则;
voidassignRule(LayoutRule rule) {
rules.add(rule);
}
设置assignedRules(){
返回规则;
}
}
类 Net 扩展了 AbstractNet {
私有总线总线;
设置assignedRules(){
设置结果 = 新的HashSet();
结果.addAll(super.assignedRules());
结果.addAll(bus.assignedRules());
返回结果;
}
}
abstract class AbstractNet {
private Set rules;
void assignRule(LayoutRule rule) {
rules.add(rule);
}
Set assignedRules() {
return rules;
}
}
class Net extends AbstractNet {
private Bus bus;
Set assignedRules() {
Set result = new HashSet();
result.addAll(super.assignedRules());
result.addAll(bus.assignedRules());
return result;
}
}
当然,还会有大量的支持代码,但这涵盖了脚本的基本功能。
Of course, there would be a great deal of supporting code, but this covers the basic functionality of the script.
该应用程序需要导入/导出逻辑,我们将其封装到一些简单的服务中。
The application requires import/export logic, which we’ll encapsulate into some simple services.
我们还需要一些实用程序:
We’ll also need a few utilities:
现在,启动应用程序只需使用导入的数据初始化存储库:
Now, starting the application is a matter of initializing the repositories with imported data:
集合 nets = NetListImportService.read(aFile);
NetRepository.addAll(nets);
集合 bus = InferredBusFactory.groupIntoBuses(nets);
BusRepository.addAll(buses);
Collection nets = NetListImportService.read(aFile);
NetRepository.addAll(nets);
Collection buses = InferredBusFactory.groupIntoBuses(nets);
BusRepository.addAll(buses);
每个服务和存储库都可以进行单元测试。更重要的是,可以测试核心域逻辑。以下是最核心行为的单元测试(使用 JUnit 测试框架):
Each of the services and repositories can be unit-tested. Even more important, the core domain logic can be tested. Here is a unit test of the most central behavior (using the JUnit test framework):
public void testBusRuleAssignment() {
Net a0 = new Net("a0");
Net a1 = new Net("a1");
Bus a = new Bus("a"); //Bus 在概念上不依赖于
a.addNet(a0); //基于名称的识别,因此
a.addNet(a1); //其测试也不应如此。
NetRule minWidth4 = NetRule.create(MIN_WIDTH, 4);
a.assignRule(minWidth4);
assertTrue(a0.assignedRules().contains(minWidth4));
assertEquals(minWidth4, a0.getRule(MIN_WIDTH));
assertEquals(minWidth4, a1.getRule(MIN_WIDTH));
}
public void testBusRuleAssignment() {
Net a0 = new Net("a0");
Net a1 = new Net("a1");
Bus a = new Bus("a"); //Bus is not conceptually dependent
a.addNet(a0); //on name-based recognition, and so
a.addNet(a1); //its tests should not be either.
NetRule minWidth4 = NetRule.create(MIN_WIDTH, 4);
a.assignRule(minWidth4);
assertTrue(a0.assignedRules().contains(minWidth4));
assertEquals(minWidth4, a0.getRule(MIN_WIDTH));
assertEquals(minWidth4, a1.getRule(MIN_WIDTH));
}
交互式用户界面可以显示总线列表,允许用户为每条总线分配规则,也可以从规则文件中读取以实现向后兼容。外观使任一接口的访问变得简单。其实现与测试相呼应:
An interactive user interface could present a list of buses, allowing the user to assign rules to each, or it could read from a file of rules for backward compatibility. A façade makes access simple for either interface. Its implementation echoes the test:
public voidassignBusRule(String busName, String ruleType,
double 参数){
Bus bus = BusRepository.getByName(busName);
bus.assignRule(NetRule.create(ruleType, 参数));
}
public void assignBusRule(String busName, String ruleType,
double parameter){
Bus bus = BusRepository.getByName(busName);
bus.assignRule(NetRule.create(ruleType, parameter));
}
精加工:
Finishing:
NetRuleExport.write(aFileName, NetRepository.allNets());
NetRuleExport.write(aFileName, NetRepository.allNets());
(该服务向每个网络请求,然后assignedRules()将它们完全展开。)
(The service asks each Net for assignedRules(), and then writes them fully expanded.)
当然,如果只有一个操作(如示例中所示),基于脚本的方法可能同样实用。但实际上,有 20 个或更多。模型驱动设计可以轻松扩展,并且可以包含对组合规则和其他增强功能的约束。
Of course, if there were only one operation (as in the example), the script-based approach might be just as practical. But in reality, there were 20 or more. The MODEL-DRIVEN DESIGN scales easily and can include constraints on combining rules and other enhancements.
第二种设计也适用于测试。其组件具有定义良好的接口,可以进行单元测试。测试脚本的唯一方法是进行端到端文件输入/文件输出比较。
The second design also accommodates testing. Its components have well-defined interfaces that can be unit-tested. The only way to test the script is to do an end-to-end file-in/file-out comparison.
请记住,这样的设计并非一步到位。需要经过多次重构和知识梳理,才能将领域的重要概念提炼为简单、精辟的模型。
Keep in mind that such a design does not emerge in a single step. It would take several iterations of refactoring and knowledge crunching to distill the important concepts of the domain into a simple, incisive model.
理论上,也许你可以向用户展示系统的任何视图,而不管其底层是什么。但在实践中,不匹配在最好的情况下会造成混乱,在最坏的情况下则会导致错误。考虑一个非常简单的例子,说明用户是如何被当前版本的 Microsoft Internet Explorer 中网站书签的叠加模型误导的。1
In theory, perhaps, you could present a user with any view of a system, regardless of what lies beneath. But in practice, a mismatch causes confusion at best—bugs at worst. Consider a very simple example of how users are misled by superimposed models of bookmarks for Web sites in current releases of Microsoft Internet Explorer.1
Internet Explorer 用户将“收藏夹”视为在会话之间持续存在的网站名称列表。但实现将收藏夹视为包含 URL 的文件,其文件名放在收藏夹列表中。如果网页标题包含 Windows 文件名中非法的字符,则会出现问题。假设用户尝试存储收藏夹并为其输入以下名称:“懒惰:幸福的秘诀”。错误消息将显示:“文件名不能包含以下任何字符:\ / : * ? " < > | ”。什么文件名?另一方面,如果网页标题已经包含非法字符,Internet Explorer 会悄悄地将其删除。在这种情况下,数据丢失可能是无害的,但不是用户所期望的。在大多数应用程序中,悄悄更改数据是完全不可接受的。
A user of Internet Explorer thinks of “Favorites” as a list of names of Web sites that persist from session to session. But the implementation treats a Favorite as a file containing a URL, and whose filename is put in the Favorites list. That’s a problem if the Web page title contains characters that are illegal in Windows filenames. Suppose a user tries to store a Favorite and types the following name for it: “Laziness: The Secret to Happiness”. An error message will say: “A filename cannot contain any of the following characters: \ / : * ? " < > | ”. What filename? On the other hand, if the Web page title already contains an illegal character, Internet Explorer will just quietly strip it out. The loss of data may be benign in this case, but not what the user would have expected. Quietly changing data is completely unacceptable in most applications.
模型驱动设计要求只使用一个模型(在任何单一环境中,如第 14 章所述)。大多数建议和示例都涉及拥有单独的分析模型和设计模型的问题,但这里我们面临的问题来自不同的模型对:用户模型和设计/实施模型。
MODEL-DRIVEN DESIGN calls for working with only one model (within any single context, as will be discussed in Chapter 14). Most of the advice and examples go to the problems of having separate analysis models and design models, but here we have a problem arising from a different pair of models: the user model and the design/implementation model.
当然,在大多数情况下,域模型的未修饰视图肯定不会给用户带来方便。但是,尝试在 UI 中创建域模型以外的模型的幻觉会导致混淆,除非这种幻觉是完美的。如果 Web 收藏夹实际上只是快捷方式文件的集合,那么请向用户公开这一事实并消除令人困惑的替代模型。该功能不仅不会那么令人困惑,而且用户还可以利用他对文件系统的了解来处理 Web 收藏夹。例如,他可以使用文件资源管理器重新组织它们,而不是使用 Web 浏览器中内置的笨拙工具。知情的用户将更能够利用将 Web 快捷方式存储在文件系统中任何位置的灵活性。只需删除误导性的额外模型,应用程序的功能就会增加并变得更加清晰。当程序员认为旧模型已经足够好时,为什么要让用户学习新模型?
Of course, an unadorned view of the domain model would definitely not be convenient for the user in most cases. But trying to create in the UI an illusion of a model other than the domain model will cause confusion unless the illusion is perfect. If Web Favorites are actually just a collection of shortcut files, then expose this fact to the user and eliminate the confusing alternative model. Not only will the feature be less confusing, but the user can then leverage what he knows about the file system to deal with Web Favorites. He can reorganize them with the File Explorer, for example, rather than use awkward tools built into the Web browser. Informed users would be more able to exploit the flexibility of storing Web shortcuts anywhere in the file system. Just by removing the misleading extra model, the power of the application would increase and become clearer. Why make the user learn a new model when the programmers felt the old model was good enough?
或者,以不同的方式存储收藏夹,比如在数据文件中,这样它们就可以遵循自己的规则。这些规则可能是适用于网页的命名规则。这将再次提供单一模型。该模型告诉用户,他所知道的有关网站命名的所有内容都适用于收藏夹。
Alternatively, store the Favorites in a different way, say in a data file, so that they can be subject to their own rules. Those rules would presumably be the naming rules that apply to Web pages. That would again provide a single model. This one tells the user that everything he knows about naming Web sites applies to Favorites.
当设计基于反映用户和领域专家基本关注点的模型时,设计的核心可以比其他设计方法更大程度地向用户展示。展示模型使用户能够更多地了解软件的潜力,并产生一致、可预测的行为。
When a design is based on a model that reflects the basic concerns of the users and domain experts, the bones of the design can be revealed to the user to a greater extent than with other design approaches. Revealing the model gives the user more access to the potential of the software and yields consistent, predictable behavior.
制造业是软件开发的一个流行比喻。从这个比喻中可以得出一个推论:技术精湛的工程师负责设计,技术水平较低的工人负责组装产品。这个比喻搞砸了很多项目,原因很简单——软件开发就是设计。所有团队都有专门的成员角色,但分析、建模、设计和编程职责的过度分离会干扰模型驱动设计。
Manufacturing is a popular metaphor for software development. One inference from this metaphor: highly skilled engineers design; less skilled laborers assemble the products. This metaphor has messed up a lot of projects for one simple reason—software development is all design. All teams have specialized roles for members, but overseparation of responsibility for analysis, modeling, design, and programming interferes with MODEL-DRIVEN DESIGN.
在一个项目中,我的工作是协调不同的应用程序团队,并帮助开发驱动设计的领域模型。但管理层认为建模者应该进行建模,而编码则浪费这些技能,因此我实际上被禁止编程或与程序员一起处理细节。
On one project, my job was to coordinate different application teams and help develop the domain model that would drive the design. But the management thought that modelers should be modeling, and that coding was a waste of those skills, so I was in effect forbidden to program or work on details with programmers.
一段时间内,一切似乎都还好。我们与领域专家和不同团队的开发主管合作,整合知识并完善了一个不错的核心模型。但由于两个原因,该模型从未投入使用。
Things seemed to be OK for a while. Working with domain experts and the development leads of the different teams, we crunched knowledge and refined a nice core model. But that model was never put to work, for two reasons.
首先,模型的部分意图在交接过程中丢失了。模型的整体效果可能对细节非常敏感(如第II和III部分所述),而这些细节并不总是出现在 UML 图或一般讨论中。如果我可以撸起袖子直接与其他开发人员合作,提供一些代码作为示例,并提供一些密切的支持,团队就可以采用模型的抽象并与之一起运行。
First, some of the model’s intent was lost in the handoff. The overall effect of a model can be very sensitive to details (as will be discussed in Parts II and III), and those details don’t always come across in a UML diagram or a general discussion. If I could have rolled up my sleeves and worked with the other developers directly, providing some code to follow as examples, and providing some close support, the team could have taken up the abstractions of the model and run with them.
另一个问题是模型与实施和技术交互的反馈不直接。例如,模型的某些方面在我们的技术平台上效率极低,但几个月后我才意识到全部影响。相对较小的改动可以解决问题,但那时已经无关紧要了。开发人员已经能够编写出可以运行的软件了——没有模型,模型已经沦为一个纯粹的数据结构,无论它在哪里仍然被使用。开发人员已经把孩子和洗澡水一起倒掉了,但他们还有什么选择呢?他们不能再冒险被象牙塔里的建筑师的指令所束缚。
The other problem was the indirectness of feedback from the interaction of the model with the implementation and the technology. For example, certain aspects of the model turned out to be wildly inefficient on our technology platform, but the full implications didn’t trickle back to me for months. Relatively minor changes could have fixed the problem, but by then it didn’t matter. The developers were well on their way to writing software that did work—without the model, which had been reduced to a mere data structure, wherever it was still used at all. The developers had thrown the baby out with the bathwater, but what choice did they have? They could no longer risk being saddled with the dictates of the architect in the ivory tower.
这个项目的初始环境对不干涉的建模者来说非常有利。我已经对项目中使用的大多数技术有了丰富的实践经验。在我改变角色之前,我甚至领导过一个小型开发团队,所以我熟悉项目的开发流程和编程环境。考虑到建模者和实施者的分离,即使是这些因素也不足以让我发挥作用。
The initial circumstances of this project were about as favorable to a hands-off modeler as they ever are. I already had extensive hands-on experience with most of the technology used on the project. I had even led a small development team on the same project before my role changed, so I was familiar with the project’s development process and programming environment. Even those factors were not enough to make me effective, given the separation of modeler from implementation.
如果编写代码的人不觉得对模型负有责任,或者不理解如何让模型适用于应用程序,那么模型就与软件毫无关系。如果开发人员没有意识到更改代码会改变模型,那么他们的重构将削弱模型,而不是增强模型。同时,当建模者脱离实施过程时,他或她永远不会获得或很快失去对实施约束的感觉。模型驱动设计的基本约束 — 模型支持有效实施并抽象关键领域知识 — 已经消失一半,产生的模型将不切实际。最后,如果分工阻碍了传达模型驱动设计编码微妙之处的那种协作,经验丰富的设计师的知识和技能将无法转移到其他开发人员身上。
If the people who write the code do not feel responsible for the model, or don’t understand how to make the model work for an application, then the model has nothing to do with the software. If developers don’t realize that changing code changes the model, then their refactoring will weaken the model rather than strengthen it. Meanwhile, when a modeler is separated from the implementation process, he or she never acquires, or quickly loses, a feel for the constraints of implementation. The basic constraint of MODEL-DRIVEN DESIGN—that the model supports an effective implementation and abstracts key domain knowledge—is half-gone, and the resulting models will be impractical. Finally, the knowledge and skills of experienced designers won’t be transferred to other developers if the division of labor prevents the kind of collaboration that conveys the subtleties of coding a MODEL-DRIVEN DESIGN.
需要动手建模者并不意味着团队成员不能有专门的角色。每个敏捷过程(包括极限编程)都为团队成员定义了角色,其他非正式专业化往往会自然出现。问题源于分离模型驱动设计中耦合的两个任务,即建模和实施。
The need for HANDS-ON MODELERS does not mean that team members cannot have specialized roles. Every Agile process, including Extreme Programming, defines roles for team members, and other informal specializations tend to emerge naturally. The problem arises from separating two tasks that are coupled in a MODEL-DRIVEN DESIGN, modeling and implementation.
总体设计的有效性对细粒度设计和实施决策的质量和一致性非常敏感。在模型驱动设计中,部分代码是模型的表达;更改该代码会更改模型。无论有人喜欢与否,程序员都是建模者。因此,最好设置项目,以便程序员能够很好地完成建模工作。
The effectiveness of an overall design is very sensitive to the quality and consistency of fine-grained design and implementation decisions. With a MODEL-DRIVEN DESIGN, a portion of the code is an expression of the model; changing that code changes the model. Programmers are modelers, whether anyone likes it or not. So it is better to set up the project so that the programmers do good modeling work.
所以:
Therefore:
任何为模型做出贡献的技术人员都必须花一些时间接触代码,无论他或她扮演的主要角色是什么项目上。负责更改代码的任何人都必须学会通过代码表达模型。每个开发人员都必须参与有关模型的某种程度的讨论,并与领域专家保持联系。以不同方式做出贡献的人必须有意识地通过UBIQUITOUS LANGUAGE与接触代码的人进行动态的模型思想交流。
Any technical person contributing to the model must spend some time touching the code, whatever primary role he or she plays on the project. Anyone responsible for changing code must learn to express a model through the code. Every developer must be involved in some level of discussion about the model and have contact with domain experts. Those who contribute in different ways must consciously engage those who touch the code in a dynamic exchange of model ideas through the UBIQUITOUS LANGUAGE.
严格区分建模和编程是行不通的,但大型项目仍然需要技术领导者来协调高级设计和建模,并帮助制定最困难或最关键的决策。第 IV 部分“战略设计”处理此类决策,并应激发人们以更有效的方式定义高级技术人员的角色和职责。
The sharp separation of modeling and programming doesn’t work, yet large projects still need technical leaders who coordinate high-level design and modeling and help work out the most difficult or most critical decisions. Part IV, “Strategic Design,” deals with such decisions and should stimulate ideas for more productive ways to define the roles and responsibilities of high-level technical people.
领域驱动设计让模型发挥作用来解决应用程序的问题。通过知识处理,团队将大量混乱的信息提炼成实用的模型。模型驱动设计将模型和实现紧密联系在一起。通用语言是所有这些信息在开发人员、领域专家和软件之间流动的渠道。
Domain-driven design puts a model to work to solve problems for an application. Through knowledge crunching, a team distills a torrent of chaotic information into a practical model. A MODEL-DRIVEN DESIGN intimately connects the model and the implementation. The UBIQUITOUS LANGUAGE is the channel for all that information to flow between developers, domain experts, and the software.
其结果是基于对核心领域的基本理解而提供丰富功能的软件。
The result is software that provides rich functionality based on a fundamental understanding of the core domain.
如上所述,模型驱动设计的成功取决于详细的设计决策,这是接下来几章的主题。
As mentioned, success with MODEL-DRIVEN DESIGN is sensitive to detailed design decisions, which is the subject of the next several chapters.
尽管现实情况很复杂,但为了使软件实现清晰并与模型保持一致,您必须应用建模和设计的最佳实践。本书不是面向对象设计的入门书,也不提出激进的设计基础。领域驱动设计改变了某些传统思想的重点。
To keep a software implementation crisp and in lockstep with a model, in spite of messy realities, you must apply the best practices of modeling and design. This book is not an introduction to object-oriented design, nor does it propose radical design fundamentals. Domain-driven design shifts the emphasis of certain conventional ideas.
某些类型的决策使模型和实现保持一致,相互增强对方的有效性。这种一致性需要关注各个元素的细节。在这种小规模上精心设计为开发人员提供了一个稳定的平台,使他们能够应用第III 部分和第 IV部分的建模方法。
Certain kinds of decisions keep the model and implementation aligned with each other, each reinforcing the other’s effectiveness. This alignment requires attention to the details of individual elements. Careful crafting at this small scale gives developers a steady platform from which to apply the modeling approaches of Parts III and IV.
本书的设计风格主要遵循Wirfs-Brock 等人于 1990 年提出并在Wirfs-Brock 2003 年更新的“责任驱动设计”原则。它还大量借鉴了Meyer 1988 年描述的“契约式设计”思想(尤其是在第三部分)。它与其他被广泛接受的面向对象设计最佳实践的一般背景相一致,这些最佳实践在Larman 1998 年等书中进行了描述。
The design style in this book largely follows the principle of “responsibility-driven design,” put forward in Wirfs-Brock et al. 1990 and updated in Wirfs-Brock 2003. It also draws heavily (especially in Part III) on the ideas of “design by contract” described in Meyer 1988. It is consistent with the general background of other widely held best practices of object-oriented design, which are described in such books as Larman 1998.
当项目遇到大大小小的困难时,开发人员可能会发现自己处于一些情况,这些原则似乎不适用。为了使领域驱动设计流程具有弹性,开发人员需要了解众所周知的基本原理如何支持模型驱动设计,这样他们就可以妥协而不会脱轨。
As a project hits bumps, large or small, developers may find themselves in situations that make those principles seem inapplicable. To make the domain-driven design process resilient, developers need to understand how the well-known fundamentals support MODEL-DRIVEN DESIGN, so they can compromise without derailing.
接下来三章的材料被组织为一种“模式语言”(参见附录 A),它将展示细微的模型区别和设计决策如何影响领域驱动设计过程。
The material in the following three chapters is organized as a “pattern language” (see Appendix A), which will show how subtle model distinctions and design decisions affect the domain-driven design process.
下一页顶部的图表是导航图。它显示了本节将介绍的模式以及它们相互关联的几种方式。
The diagram on the top of the next page is a navigation map. It shows the patterns that will be presented in this section and a few of the ways they relate to each other.
共享这些标准模式使设计井然有序,团队成员也更容易理解彼此的工作。使用标准模式还增加了通用语言,所有团队成员都可以使用它来讨论模型和设计决策。
Sharing these standard patterns brings order to the design and makes it easier for team members to understand each other’s work. Using standard patterns also adds to the UBIQUITOUS LANGUAGE, which all team members can use to discuss model and design decisions.
开发一个好的领域模型是一门艺术。但模型各个元素的实际设计和实现可以相对系统化。将领域设计与其他大量关注点隔离开来软件系统中的元素定义将极大地明确设计与模型的联系。根据某些区别来定义模型元素可以使其含义更加清晰。遵循各个元素的成熟模式有助于生成切实可行的模型。
Developing a good domain model is an art. But the practical design and implementation of a model’s individual elements can be relatively systematic. Isolating the domain design from the mass of other concerns in the software system will greatly clarify the design’s connection to the model. Defining model elements according to certain distinctions sharpens their meanings. Following proven patterns for individual elements helps produce a model that is practical to implement.
模型驱动设计语言的导航图
A navigation map of the language of MODEL-DRIVEN DESIGN
只有注重基本原理,精心设计的模型才能消除复杂性,从而产生团队可以自信地组合起来的细节元素。
Elaborate models can cut through complexity only if care is taken with the fundamentals, resulting in detailed elements that the team can confidently combine.
软件中专门解决领域问题的部分通常只构成整个软件系统的一小部分,尽管其重要性与其规模不成比例。为了运用我们的最佳思维,我们需要能够查看模型的元素并将它们视为一个系统。我们绝不能被迫从更大的对象组合中挑选它们,就像试图识别夜空中的星座一样。我们需要将领域对象与系统的其他功能分离,这样我们就可以避免将领域概念与仅与软件技术相关的其他概念混淆,或者在系统整体中完全忽视领域。
The part of the software that specifically solves problems from the domain usually constitutes only a small portion of the entire software system, although its importance is disproportionate to its size. To apply our best thinking, we need to be able to look at the elements of our model and see them as a system. We must not be forced to pick them out of a much larger mix of objects, like trying to identify constellations in the night sky. We need to decouple the domain objects from other functions of the system, so we can avoid confusing the domain concepts with other concepts related only to software technology or losing sight of the domain altogether in the mass of the system.
已经出现了用于这种隔离的复杂技术。这是一个老生常谈的领域,但它对于成功应用领域建模原则至关重要,因此必须从领域驱动的角度对其进行简要回顾。...
Sophisticated techniques for this isolation have emerged. This is well-trodden ground, but it is so critical to the successful application of domain-modeling principles that it must be reviewed briefly, from a domain-driven point of view. . . .
要使运输应用程序支持用户从城市列表中选择货物目的地这一简单操作,必须有以下程序代码:(1) 在屏幕上绘制小部件,(2) 查询数据库中所有可能的城市,(3) 解释用户的输入并进行验证,(4) 将选定的城市与货物关联,以及 (5) 将更改提交给数据库。所有这些代码都是同一程序的一部分,但只有一小部分与运输业务相关。
For a shipping application to support the simple user act of selecting a cargo’s destination from a list of cities, there must be program code that (1) draws a widget on the screen, (2) queries the database for all the possible cities, (3) interprets the user’s input and validates it, (4) associates the selected city with the cargo, and (5) commits the change to the database. All of this code is part of the same program, but only a little of it is related to the business of shipping.
软件程序涉及设计和代码,以执行多种不同类型的任务。它们接受用户输入、执行业务逻辑、访问数据库、通过网络通信、向用户显示信息等等。因此,每个程序功能所涉及的代码可能非常庞大。
Software programs involve design and code to carry out many different kinds of tasks. They accept user input, carry out business logic, access databases, communicate over networks, display information to users, and so on. So the code involved in each program function can be substantial.
在面向对象的程序中,UI、数据库和其他支持代码通常直接写入业务对象中。额外的业务逻辑嵌入在 UI 小部件和数据库脚本的行为中。之所以发生这种情况,是因为从短期来看,这是使事情运转起来最简单的方法。
In an object-oriented program, UI, database, and other support code often gets written directly into the business objects. Additional business logic is embedded in the behavior of UI widgets and database scripts. This happens because it is the easiest way to make things work, in the short run.
当领域相关代码分散在大量其他代码中时,就变得非常难以查看和推理。对 UI 的简单更改实际上会改变业务逻辑。要更改业务规则可能需要对 UI 代码、数据库代码或其他程序元素进行细致的跟踪。实现连贯的模型驱动对象变得不切实际。自动化测试很尴尬。由于每个活动都涉及所有技术和逻辑,因此程序必须保持非常简单,否则就无法理解。
When the domain-related code is diffused through such a large amount of other code, it becomes extremely difficult to see and to reason about. Superficial changes to the UI can actually change business logic. To change a business rule may require meticulous tracing of UI code, database code, or other program elements. Implementing coherent, model-driven objects becomes impractical. Automated testing is awkward. With all the technologies and logic involved in each activity, a program must be kept very simple or it becomes impossible to understand.
要创建能够处理非常复杂任务的程序,就需要将关注点分离,以便将注意力集中在设计的不同部分上。同时,尽管进行了分离,但系统内复杂的交互仍必须得到维护。
Creating programs that can handle very complex tasks calls for separation of concerns, allowing concentration on different parts of the design in isolation. At the same time, the intricate interactions within the system must be maintained in spite of the separation.
软件系统有各种各样的划分方式,但根据经验和惯例,业界已采用分层架构,特别是几个相当标准的层。分层这个比喻被广泛使用,对大多数开发人员来说都很直观。文献中有很多关于分层的很好的讨论,有时以模式的形式出现(如Buschmann 等人,1996 年,第 31-51 页)。基本原则是,某一层的任何元素仅依赖于同一层中的其他元素或其“下”层的元素。向上的通信必须通过某种间接机制,我稍后将讨论。
There are all sorts of ways a software system might be divided, but through experience and convention, the industry has converged on LAYERED ARCHITECTURES, and specifically a few fairly standard layers. The metaphor of layering is so widely used that it feels intuitive to most developers. Many good discussions of layering are available in the literature, sometimes in the format of a pattern (as in Buschmann et al. 1996, pp. 31–51). The essential principle is that any element of a layer depends only on other elements in the same layer or on elements of the layers “beneath” it. Communication upward must pass through some indirect mechanism, which I’ll discuss a little later.
层的价值在于,每个层都专注于计算机程序的某个特定方面。这种专业化使得每个方面设计得更具凝聚力,也使得这些设计更容易解释。当然,选择隔离最重要的凝聚力设计方面的层至关重要。同样,经验和惯例也导致了一些趋同。尽管存在许多变化,但大多数成功的架构都使用以下四个概念层的某个版本:
The value of layers is that each specializes in a particular aspect of a computer program. This specialization allows more cohesive designs of each aspect, and it makes these designs much easier to interpret. Of course, it is vital to choose layers that isolate the most important cohesive design aspects. Again, experience and convention have led to some convergence. Although there are many variations, most successful architectures use some version of these four conceptual layers:
有些项目没有在用户界面和应用层之间做出明确区分。其他则有多个基础设施层。但正是领域层的分离才使得模型驱动设计成为可能。
Some projects don’t make a sharp distinction between the user interface and application layers. Others have multiple infrastructure layers. But it is the crucial separation of the domain layer that enables MODEL-DRIVEN DESIGN.
所以:
Therefore:
将复杂程序划分为多个层。在每一层内开发一个具有凝聚力且仅依赖于下层的设计。遵循标准架构模式,以提供与上层的松散耦合。将与领域模型相关的所有代码集中在一个层中,并将其与用户界面、应用程序和基础结构代码隔离开来。领域对象无需承担显示自身、存储自身、管理应用程序任务等责任,可以专注于表达领域模型。这使得模型能够发展得足够丰富和清晰,以捕获必要的业务知识并使其发挥作用。
Partition a complex program into layers. Develop a design within each layer that is cohesive and that depends only on the layers below. Follow standard architectural patterns to provide loose coupling to the layers above. Concentrate all the code related to the domain model in one layer and isolate it from the user interface, application, and infrastructure code. The domain objects, free of the responsibility of displaying themselves, storing themselves, managing application tasks, and so forth, can be focused on expressing the domain model. This allows a model to evolve to be rich enough and clear enough to capture essential business knowledge and put it to work.
将领域层与基础架构层和用户界面层分开,可以使每个层的设计更加简洁。隔离的层维护起来成本要低得多,因为它们往往以不同的速度发展并响应不同的需求。这种分离还有助于在分布式系统中部署,因为它允许将不同的层灵活地放置在不同的服务器或客户端中,从而最大限度地减少通信开销并提高性能(Fowler 1996)。
Separating the domain layer from the infrastructure and user interface layers allows a much cleaner design of each layer. Isolated layers are much less expensive to maintain, because they tend to evolve at different rates and respond to different needs. The separation also helps with deployment in a distributed system, by allowing different layers to be placed flexibly in different servers or clients, in order to minimize communication overhead and improve performance (Fowler 1996).
应用程序提供各种维护银行账户的功能。其中一项功能是资金转账,用户输入或选择两个账号和金额,然后发起转账。
An application provides various capabilities for maintaining bank accounts. One feature is funds transfer, in which the user enters or chooses two account numbers and an amount of money and then initiates a transfer.
为了使此示例易于管理,我省略了主要的技术特性,尤其是安全性。域设计也过于简单。(现实的复杂性只会增加对分层架构的需求。)此外,此处暗示的特定基础架构旨在简单明了,以使示例清晰明了 - 它不是建议的设计。其余功能的职责将按图4.1所示分层。
To make this example manageable, I’ve omitted major technical features, most notably security. The domain design is also oversimplified. (Realistic complexity would only increase the need for layered architecture.) Furthermore, the particular infrastructure implied here is meant to be simple and obvious to make the example clear—it is not a suggested design. The responsibilities of the remaining functionality would be layered as shown in Figure 4.1.
图 4.1。对象履行与其层一致的职责,并且与其层中的其他对象更加耦合。
Figure 4.1. Objects carry out responsibilities consistent with their layer and are more coupled to other objects in their layer.
请注意,领域层(而不是应用层)负责基本业务规则 - 在本例中,规则是“每一笔贷记都有相应的借记”。
Note that the domain layer, not the application layer, is responsible for fundamental business rules—in this case, the rule is “Every credit has a matching debit.”
应用程序也没有对转账请求的来源做出任何假设。程序可能包含一个 UI,其中包含用于输入帐号和金额的字段以及用于输入命令的按钮。但是,该用户界面可以用 XML 中的有线请求替换,而不会影响应用程序层或任何较低层。这种解耦很重要,不是因为项目经常需要用有线请求替换用户界面,而是因为清晰的关注点分离使每一层的设计易于理解和维护。
The application also makes no assumptions about the source of the transfer request. The program presumably includes a UI with entry fields for account numbers and amounts and with buttons for commands. But that user interface could be replaced by a wire request in XML without affecting the application layer or any of the lower layers. This decoupling is important not because projects frequently need to replace user interfaces with wire requests but because a clean separation of concerns keeps the design of each layer easy to understand and maintain.
事实上,图 4.1 本身就温和地说明了不隔离域的问题。因为必须包括从请求到交易控制的所有内容,所以必须简化域层以保持整体交互足够简单易懂。如果我们专注于隔离域层的设计,我们将在页面上和头脑中留出空间来设计一个更好地代表域规则的模型,可能包括分类账、信用和借记对象或货币交易对象。
In fact, Figure 4.1 itself mildly illustrates the problem of not isolating the domain. Because everything from the request to transaction control had to be included, the domain layer had to be dumbed down to keep the overall interaction simple enough to follow. If we were focused on the design of the isolated domain layer, we would have space on the page and in our heads for a model that better represented the domain’s rules, perhaps including ledgers, credit and debit objects, or monetary transaction objects.
到目前为止,讨论主要集中在层的分离以及这种划分如何改善程序各个方面(尤其是领域层)的设计。但当然,这些层必须连接起来。实现这一点而不失去分离的好处是许多模式背后的动机。
So far the discussion has focused on the separation of layers and the way in which that partitioning improves the design of each aspect of the program, particularly the domain layer. But of course, the layers have to be connected. To do this without losing the benefit of the separation is the motivation behind a number of patterns.
各层应松散耦合,设计依赖关系只在一个方向上。上层可以使用或操纵通过调用公共接口、保存对它们的引用(至少是暂时的)以及通常使用常规交互方式,我们可以直接与较低级别的元素通信。但是,当较低级别的对象需要向上通信时(不仅仅是回答直接查询),我们需要另一种机制,利用架构模式来关联层,例如回调或OBSERVER(Gamma 等人,1995 年)。
Layers are meant to be loosely coupled, with design dependencies in only one direction. Upper layers can use or manipulate elements of lower ones straightforwardly by calling their public interfaces, holding references to them (at least temporarily), and generally using conventional means of interaction. But when an object of a lower level needs to communicate upward (beyond answering a direct query), we need another mechanism, drawing on architectural patterns for relating layers such as callbacks or OBSERVERS (Gamma et al. 1995).
将 UI 连接到应用程序和域层的模式的鼻祖是模型-视图-控制器(MVC)。它是在 20 世纪 70 年代 Smalltalk 世界中首创的,并启发了后来的许多 UI 架构。Fowler (2003) 讨论了这种模式以及该主题的几种有用变体。Larman (1998)在模型-视图分离模式中探讨了这些问题,他的应用程序协调器是连接应用程序层的一种方法。
The grandfather of patterns for connecting the UI to the application and domain layers is MODEL-VIEW-CONTROLLER (MVC). It was pioneered in the Smalltalk world back in the 1970s and has inspired many of the UI architectures that followed. Fowler (2003) discusses this pattern and several useful variations on the theme. Larman (1998) explores these concerns in the MODEL-VIEW SEPARATION PATTERN, and his APPLICATION COORDINATOR is one approach to connecting the application layer.
还有其他连接 UI 和应用程序的方式。就我们的目的而言,所有方法都可以,只要它们保持域层的隔离,允许在设计域对象时无需同时考虑可能与它们交互的用户界面。
There are other styles of connecting the UI and the application. For our purposes, all approaches are fine as long as they maintain the isolation of the domain layer, allowing domain objects to be designed without simultaneously thinking about the user interface that might interact with them.
基础设施层通常不会在领域层中发起操作。由于位于领域层“之下”,它不应该对其所服务的领域具有任何特定的知识。事实上,这种技术能力通常以服务的形式提供。例如,如果应用程序需要发送电子邮件,则可以在基础设施层中设置某个消息发送接口,然后应用程序层元素可以请求传输该消息。这种解耦提供了一些额外的多功能性。消息发送接口可以连接到电子邮件发送器、传真发送器或其他任何可用的设备。但主要的好处是简化应用程序层,使其只需专注于其工作:知道何时发送消息,而不必担心如何发送。
The infrastructure layer usually does not initiate action in the domain layer. Being “below” the domain layer, it should have no specific knowledge of the domain it is serving. Indeed, such technical capabilities are most often offered as SERVICES. For example, if an application needs to send an e-mail, some message-sending interface can be located in the infrastructure layer and the application layer elements can request the transmission of the message. This decoupling gives some extra versatility. The message-sending interface might be connected to an e-mail sender, a fax sender, or whatever else is available. But the main benefit is simplifying the application layer, keeping it narrowly focused on its job: knowing when to send a message, but not burdened with how.
应用程序层和领域层调用基础设施层提供的服务。当服务的范围选择得当,其接口设计得当,调用者可以保持松散耦合,并且不会因服务接口封装的复杂行为而变得复杂。
The application and domain layers call on the SERVICES provided by the infrastructure layer. When the scope of a SERVICE has been well chosen and its interface well designed, the caller can remain loosely coupled and uncomplicated by the elaborate behavior the SERVICE interface encapsulates.
但并非所有基础设施都以可从更高层调用的服务形式出现。一些技术组件旨在直接支持其他层的基本功能(例如为所有域对象提供抽象基类)并提供它们关联的机制(例如 MVC 的实现等)。这样的“架构框架”对程序其他部分的设计影响更大。
But not all infrastructure comes in the form of SERVICES callable from the higher layers. Some technical components are designed to directly support the basic functions of other layers (such as providing an abstract base class for all domain objects) and provide the mechanisms for them to relate (such as implementations of MVC and the like). Such an “architectural framework” has much more impact on the design of the other parts of the program.
当基础架构以通过接口调用的服务形式提供时,分层的工作方式以及如何保持各层松散耦合是相当直观的。但有些技术问题需要更具侵入性的基础设施形式。集成许多基础架构需求的框架通常需要以非常特殊的方式实现其他层,例如作为框架类的子类或使用结构化方法签名。(子类位于比父类更高的层似乎违反直觉,但请记住哪个类反映了更多有关另一个类的知识。)最好的架构框架可以解决复杂的技术问题,同时允许领域开发人员专注于表达模型。但框架很容易成为阻碍,要么做出太多限制领域设计选择的假设,要么使实现过于繁重以致开发速度减慢。
When infrastructure is provided in the form of SERVICES called on through interfaces, it is fairly intuitive how the layering works and how to keep the layers loosely coupled. But some technical problems call for more intrusive forms of infrastructure. Frameworks that integrate many infrastructure needs often require the other layers to be implemented in very particular ways, for example as a subclass of a framework class or with structured method signatures. (It may seem counterintuitive for a subclass to be in a layer higher than that of the parent class, but keep in mind which class reflects more knowledge of the other.) The best architectural frameworks solve complex technical problems while allowing the domain developer to concentrate on expressing a model. But frameworks can easily get in the way, either by making too many assumptions that constrain domain design choices or by making the implementation so heavyweight that development slows down.
通常需要某种形式的架构框架(尽管有时团队会选择一些不太适合他们的框架)。在应用框架时,团队需要专注于其目标:构建一个表达领域模型的实现,并用它来解决重要问题。团队必须寻找使用框架实现这些目标的方法,即使这意味着不使用框架的所有功能。例如,早期的 J2EE 应用程序通常将所有领域对象实现为“实体 bean”。这种方法会降低性能和开发速度。相反,当前的最佳实践是将 J2EE 框架用于较大粒度的对象,使用通用 Java 对象实现大多数业务逻辑。通过有选择地应用框架来解决难题,而不是寻找一刀切的解决方案,可以避免框架的许多缺点。明智地只应用最有价值的框架功能的简化降低了实现和框架之间的耦合度,从而为后续的设计决策提供了更大的灵活性。更重要的是,考虑到当前许多框架的使用非常复杂,这种简化有助于保持业务对象的可读性和表现力。
Some form of architectural framework usually is needed (though sometimes teams choose frameworks that don’t serve them well). When applying a framework, the team needs to focus on its goal: building an implementation that expresses a domain model and uses it to solve important problems. The team must seek ways of employing the framework to those ends, even if it means not using all of the framework’s features. For example, early J2EE applications often implemented all domain objects as “entity beans.” This approach bogged down both performance and the pace of development. Instead, current best practice is to use the J2EE framework for larger grain objects, implementing most business logic with generic Java objects. A lot of the downside of frameworks can be avoided by applying them selectively to solve difficult problems without looking for a one-size-fits-all solution. Judiciously applying only the most valuable of framework features reduces the coupling of the implementation and the framework, allowing more flexibility in later design decisions. More important, given how very complicated many of the current frameworks are to use, this minimalism helps keep the business objects readable and expressive.
架构框架和其他工具将继续发展。较新的框架将自动化或预制越来越多的应用程序技术方面。如果做得好,应用程序开发人员将越来越多地将时间集中在建模核心业务问题上,从而大大提高生产力和质量。但是,当我们朝着这个方向前进时,我们必须警惕对技术解决方案的热情;复杂的框架也可能束缚应用程序开发人员。
Architectural frameworks and other tools will continue to evolve. Newer frameworks will automate or prefabricate more and more of the technical aspects of an application. If this is done right, application developers will increasingly concentrate their time on modeling the core business problems, greatly improving productivity and quality. But as we move in this direction, we must guard against our enthusiasm for technical solutions; elaborate frameworks can also straitjacket application developers.
当今大多数系统都采用分层架构,采用各种分层方案。许多开发风格也可以从分层中受益。然而,领域驱动设计只需要一个特定的层即可。
LAYERED ARCHITECTURE is used in most systems today, under various layering schemes. Many styles of development can also benefit from layering. However, domain-driven design requires only one particular layer to exist.
领域模型是一组概念。“领域层”是该模型及其所有直接相关的设计元素的体现。业务逻辑的设计和实现构成了领域层。在模型驱动设计中,领域层的软件构造反映了模型概念。
The domain model is a set of concepts. The “domain layer” is the manifestation of that model and all directly related design elements. The design and implementation of business logic constitute the domain layer. In a MODEL-DRIVEN DESIGN, the software constructs of the domain layer mirror the model concepts.
当领域逻辑与程序的其他关注点混合在一起时,实现这种对应关系是不切实际的。隔离领域实现是领域驱动设计的先决条件。
It is not practical to achieve that correspondence when the domain logic is mixed with other concerns of the program. Isolating the domain implementation is a prerequisite for domain-driven design.
……这总结了对象应用程序广泛接受的分层架构模式。但是,这种 UI、应用程序和域的分离尝试如此频繁,却很少实现,因此其否定本身就值得讨论。
. . . That sums up the widely accepted LAYERED ARCHITECTURE pattern for object applications. But this separation of UI, application, and domain is so often attempted and so seldom accomplished that its negation deserves a discussion in its own right.
许多软件项目确实采用了一种不太复杂的设计方法,我称之为SMART UI,并且应该继续采用。但SMART UI 是一条替代的、相互排斥的岔路,与领域驱动设计方法不兼容。如果走那条路,本书中的大部分内容就不适用了。我感兴趣的是 SMART UI 不适用的情况,这就是为什么我半开玩笑地称它为“反模式”。在这里讨论它提供了一个有用的对比,并将有助于澄清在本书其余部分采取更困难的道路的合理性。
Many software projects do take and should continue to take a much less sophisticated design approach that I call the SMART UI. But SMART UI is an alternate, mutually exclusive fork in the road, incompatible with the approach of domain-driven design. If that road is taken, most of what is in this book is not applicable. My interest is in the situations where the SMART UI does not apply, which is why I call it, with tongue in cheek, an “anti-pattern.” Discussing it here provides a useful contrast and will help clarify the circumstances that justify the more difficult path taken in the rest of the book.
项目需要提供简单的功能,主要由数据输入和显示组成,几乎没有业务规则。员工不是由高级对象建模者组成。
A project needs to deliver simple functionality, dominated by data entry and display, with few business rules. Staff is not composed of advanced object modelers.
如果一个拥有简单项目的不成熟的团队决定尝试使用分层架构的模型驱动设计,那么他们将面临艰难的学习曲线。团队成员必须掌握复杂的新技术,并跌跌撞撞地学习对象建模(即使借助本书,这也是一项挑战!)。管理基础架构和层的开销使非常简单的任务需要更长的时间。简单的项目往往时间紧迫,期望值也一般。在团队完成分配的任务之前,更不用说展示其方法的令人兴奋的可能性之前,项目就会被取消。
If an unsophisticated team with a simple project decides to try a MODEL-DRIVEN DESIGN with LAYERED ARCHITECTURE, it will face a difficult learning curve. Team members will have to master complex new technologies and stumble through the process of learning object modeling (which is challenging, even with the help of this book!). The overhead of managing infrastructure and layers makes very simple tasks take longer. Simple projects come with short time lines and modest expectations. Long before the team completes the assigned task, much less demonstrates the exciting possibilities of its approach, the project will have been canceled.
即使给团队更多时间,团队成员在没有专家帮助的情况下也可能无法掌握这些技术。最终,如果他们克服了这些挑战,他们只会开发出一个简单的系统。他们从未要求过丰富的功能。
Even if the team is given more time, the team members are likely to fail to master the techniques without expert help. And in the end, if they do surmount these challenges, they will have produced a simple system. Rich capabilities were never requested.
经验丰富的团队不会面临同样的权衡。经验丰富的开发人员可以降低学习曲线并压缩管理层所需的时间。领域驱动设计会带来回报最适合雄心勃勃的项目,而且确实需要很强的技能。并非所有项目都雄心勃勃。并非所有项目团队都能掌握这些技能。
A more experienced team would not face the same trade-offs. Seasoned developers could flatten the learning curve and compress the time needed to manage the layers. Domain-driven design pays off best for ambitious projects, and it does require strong skills. Not all projects are ambitious. Not all project teams can muster those skills.
因此,当情况需要时:
Therefore, when circumstances warrant:
将所有业务逻辑放入用户界面。将应用程序拆分成小功能,并将它们实现为单独的用户界面,将业务规则嵌入其中。使用关系数据库作为数据的共享存储库。使用最自动化的 UI 构建和可视化编程工具。
Put all the business logic into the user interface. Chop the application into small functions and implement them as separate user interfaces, embedding the business rules into them. Use a relational database as a shared repository of the data. Use the most automated UI building and visual programming tools available.
异端邪说!福音(正如本书其他地方所提倡的那样)是领域和 UI 应该分开。事实上,如果没有这种分离,很难应用本书后面讨论的任何方法,因此这种SMART UI 可以被视为领域驱动设计环境中的“反模式”。然而,在其他一些情况下,它是一种合法的模式。事实上, SMART UI有优势,在某些情况下它效果最好——这部分解释了为什么它如此普遍。在这里考虑它有助于我们理解为什么我们需要将应用程序与领域分开,更重要的是,我们可能不想这样做。
Heresy! The gospel (as advocated everywhere, including elsewhere in this book) is that domain and UI should be separate. In fact, it is difficult to apply any of the methods discussed later in this book without that separation, and so this SMART UI can be considered an “anti-pattern” in the context of domain-driven design. Yet it is a legitimate pattern in some other contexts. In truth, there are advantages to the SMART UI, and there are situations where it works best—which partially accounts for why it is so common. Considering it here helps us understand why we need to separate application from domain and, importantly, when we might not want to.
优点
Advantages
• 对于简单的应用来说,生产效率很高并且立即可用。
• Productivity is high and immediate for simple applications.
• 能力较差的开发人员只需经过少量培训就可以以这种方式工作。
• Less capable developers can work this way with little training.
• 即使是需求分析中的缺陷,也可以通过向用户发布原型,然后快速更改产品以满足他们的要求来克服。
• Even deficiencies in requirements analysis can be overcome by releasing a prototype to users and then quickly changing the product to fit their requests.
• 应用程序彼此分离,因此可以相对准确地规划小模块的交付时间表。使用附加的简单行为扩展系统也很容易。
• Applications are decoupled from each other, so that delivery schedules of small modules can be planned relatively accurately. Expanding the system with additional, simple behavior can be easy.
• 关系数据库运行良好并提供数据级别的集成。
• Relational databases work well and provide integration at the data level.
• 4GL 工具运行良好。
• 4GL tools work well.
• 当应用程序交付时,维护程序员将能够快速重做他们无法弄清楚的部分,因为更改的影响应该本地化到每个特定的 UI。
• When applications are handed off, maintenance programmers will be able to quickly redo portions they can’t figure out, because the effects of the changes should be localized to each particular UI.
• 除了通过数据库之外,应用程序的集成非常困难。
• Integration of applications is difficult except through the database.
• 无法重用行为,也无法抽象业务问题。业务规则必须在应用到的每个操作中重复。
• There is no reuse of behavior and no abstraction of the business problem. Business rules have to be duplicated in each operation to which they apply.
• 快速原型设计和迭代达到了自然极限,因为缺乏抽象限制了重构选项。
• Rapid prototyping and iteration reach a natural limit because the lack of abstraction limits refactoring options.
• 复杂性会很快淹没你,因此成长之路严格遵循额外的简单应用程序。没有优雅的路径来实现更丰富的行为。
• Complexity buries you quickly, so the growth path is strictly toward additional simple applications. There is no graceful path to richer behavior.
如果有意识地应用这种模式,团队可以避免承担其他方法所需的大量开销。采用复杂的设计方法,但团队不承诺完全贯彻,这是一个常见的错误。另一个常见的、代价高昂的错误是构建复杂的基础设施,并为不需要它们的项目使用工业级工具。
If this pattern is applied consciously, a team can avoid taking on a great deal of overhead required by other approaches. It is a common mistake to undertake a sophisticated design approach that the team isn’t committed to carrying all the way through. Another common, costly mistake is to build a complex infrastructure and use industrial-strength tools for a project that doesn’t need them.
大多数灵活的语言(如 Java)对于这些应用程序来说都是多余的,而且价格昂贵。4GL 风格的工具才是最佳选择。
Most flexible languages (such as Java) are overkill for these applications and will cost dearly. A 4GL-style tool is the way to go.
请记住,这种模式的后果之一是,除非替换整个应用程序,否则您无法迁移到其他设计方法。仅使用 Java 等通用语言并不能真正让您以后放弃 SMART UI ,因此如果您选择了这条路,则应该选择与之相适应的开发工具。不要费心去两面下注。仅使用灵活的语言并不能创建灵活的系统,但它可能会产生昂贵的系统。
Remember, one of the consequences of this pattern is that you can’t migrate to another design approach except by replacing entire applications. Just using a general-purpose language such as Java won’t really put you in a position to later abandon the SMART UI, so if you’ve chosen that path, you should choose development tools geared to it. Don’t bother hedging your bet. Just using a flexible language doesn’t create a flexible system, but it may well produce an expensive one.
同样,致力于模型驱动设计的团队需要从一开始就以这种方式进行设计。当然,即使是经验丰富、雄心勃勃的项目团队也必须从简单的功能开始,然后通过连续的迭代逐步完善。但这些最初的尝试步骤将是具有独立域层的模型驱动,否则项目很可能会陷入SMART UI 的泥潭。
By the same token, a team committed to a MODEL-DRIVEN DESIGN needs to design that way from the outset. Of course, even experienced project teams with big ambitions have to start with simple functionality and work their way up through successive iterations. But those first tentative steps will be MODEL-DRIVEN with an isolated domain layer, or the project will most likely be stuck with a SMART UI.
SMART 讨论 UI 仅仅是为了阐明为什么以及何时需要诸如分层架构之类的模式来隔离领域层。
The SMART UI is discussed only to clarify why and when a pattern such as LAYERED ARCHITECTURE is needed in order to isolate a domain layer.
SMART UI 和分层架构之间还有其他解决方案。例如,Fowler (2003) 描述了TRANSACTION SCRIPT,它将 UI 与应用程序分开,但不提供对象模型。底线是:如果架构以允许与系统其余部分松散耦合的内聚域设计的方式隔离与域相关的代码,那么该架构可能可以支持域驱动设计。
There are other solutions in between SMART UI and LAYERED ARCHITECTURE. For example, Fowler (2003) describes the TRANSACTION SCRIPT, which separates UI from application but does not provide for an object model. The bottom line is this: If the architecture isolates the domain-related code in a way that allows a cohesive domain design loosely coupled to the rest of the system, then that architecture can probably support domain-driven design.
其他开发风格各有千秋,但您必须接受复杂性和灵活性的不同限制。在某些情况下,无法分离域设计确实会带来灾难性的后果。如果您有一个复杂的应用程序并且致力于模型驱动设计,请咬紧牙关,聘请必要的专家,并避免使用SMART UI 。
Other development styles have their place, but you must accept varying limits on complexity and flexibility. Failing to decouple the domain design can really be disastrous in certain settings. If you have a complex application and are committing to MODEL-DRIVEN DESIGN, bite the bullet, get the necessary experts, and avoid the SMART UI.
遗憾的是,除了基础结构和用户界面之外,还有其他因素可能会破坏您精密的领域模型。您必须处理未完全集成到模型中的其他领域组件。您必须应对使用同一领域的不同模型的其他开发团队。这些因素和其他因素可能会使您的模型变得模糊不清,并使其失去实用性。第 14 章“维护模型完整性”讨论了这个主题,介绍了诸如BOUNDED CONTEXT和ANTICORRUPTION LAYER之类的模式。一个真正复杂的领域模型本身可能会变得难以处理。第 15 章“提炼”讨论了如何在领域层内进行区分,以便将领域的基本概念与外围细节区分开来。
Unfortunately, there are influences other than infrastructure and user interfaces that can corrupt your delicate domain model. You must deal with other domain components that are not fully integrated into your model. You have to cope with other development teams who use different models of the same domain. These and other factors can blur your model and rob it of its utility. Chapter 14, “Maintaining Model Integrity,” deals with this topic, introducing such patterns as BOUNDED CONTEXT and ANTICORRUPTION LAYER. A really complicated domain model can become unwieldy all by itself. Chapter 15, “Distillation,” discusses how to make distinctions within the domain layer that can unencumber the essential concepts of the domain from peripheral detail.
但这一切都是以后的事。接下来,我们将研究共同发展有效领域模型和富有表现力的实现的具体细节。毕竟,隔离领域最好的部分是把所有其他东西都排除在外,这样我们就可以真正专注于领域设计。
But all that comes later. Next, we’ll look at the nuts and bolts of co-evolving an effective domain model and an expressive implementation. After all, the best part of isolating the domain is getting all that other stuff out of the way so that we can really focus on the domain design.
要在实施中做出妥协而不失去模型驱动设计的强大功能,就需要重新构建基础。必须在细节层面上连接模型和实施。本章重点介绍这些单独的模型元素,使它们成型以支持后面章节中的活动。
To compromise in implementation without losing the punch of a MODEL-DRIVEN DESIGN requires a reframing of the basics. Connecting model and implementation has to be done at the detail level. This chapter focuses on those individual model elements, getting them in shape to support the activities in later chapters.
本讨论将从设计和简化关联的问题开始。对象之间的关联很容易构思和绘制,但实施起来却可能很困难。关联说明了详细的实施决策对于模型驱动设计的可行性有多么重要。
This discussion will start with the issues of designing and streamlining associations. Associations between objects are simple to conceive and to draw, but implementing them is a potential quagmire. Associations illustrate how crucial detailed implementation decisions are to the viability of a MODEL-DRIVEN DESIGN.
转向对象本身,但继续仔细研究详细的模型选择和实施关注之间的关系,我们将重点区分表达模型的三种模型元素模式:实体,值对象和服务。
Turning to the objects themselves, but continuing to scrutinize the relationship between detailed model choices and implementation concerns, we’ll focus on making distinctions among the three patterns of model elements that express the model: ENTITIES, VALUE OBJECTS, and SERVICES.
定义捕捉领域概念的对象表面上看起来很直观,但其含义背后隐藏着严峻的挑战。某些区别已经出现,它们阐明了模型元素的含义,并与一系列设计实践相结合,以划分特定类型的对象。
Defining objects that capture concepts of the domain seems very intuitive on the surface, but serious challenges are lurking in the shades of meaning. Certain distinctions have emerged that clarify the meaning of model elements and tie into a body of design practices for carving out specific kinds of objects.
对象是否代表具有连续性和身份的事物 — — 可以通过不同状态甚至不同实现进行跟踪的事物?或者它是描述其他事物状态的属性?这是 ENTITY和VALUE OBJECT之间的基本区别。定义明确遵循一种或另一种模式的对象可以使对象更清晰,并为稳健设计的特定选择铺平道路。
Does an object represent something with continuity and identity—something that is tracked through different states or even across different implementations? Or is it an attribute that describes the state of something else? This is the basic distinction between an ENTITY and a VALUE OBJECT. Defining objects that clearly follow one pattern or the other makes the objects less ambiguous and lays out the path toward specific choices for robust design.
然后,领域中的某些方面更清楚地表达为动作或操作,而不是对象。虽然这略微偏离了面向对象建模传统,但通常最好将它们表达为服务,而不是将操作的责任强加给某个实体或值对象。服务是根据请求为客户完成的。在软件的技术层中,有许多服务。当建模某些活动对应于软件必须执行的操作但与状态不对应时,它们也会出现在领域中。
Then there are those aspects of the domain that are more clearly expressed as actions or operations, rather than as objects. Although it is a slight departure from object-oriented modeling tradition, it is often best to express these as SERVICES, rather than forcing responsibility for an operation onto some ENTITY or VALUE OBJECT. A SERVICE is something that is done for a client on request. In the technical layers of the software, there are many SERVICES. They emerge in the domain also, when some activity is modeled that corresponds to something the software must do, but does not correspond with state.
不可避免地,在某些情况下,对象模型的纯粹性必须受到损害,例如在关系数据库中存储时。本章将列出一些指导方针,以便在您被迫处理这些混乱的现实时保持正轨。
There are inevitable situations in which the purity of the object model must be compromised, such as for storage in a relational database. This chapter will lay out some guidelines for staying on course when you are forced to deal with these messy realities.
最后,关于模块的讨论将使大家明白,每个设计决策都应由对领域的一些了解所驱动。高内聚和低耦合的理念通常被认为是技术指标,可以应用于概念本身。在模型驱动设计中,模块是模型的一部分,它们应该反映领域中的概念。
Finally, a discussion of MODULES will drive home the point that every design decision should be motivated by some insight into the domain. The ideas of high cohesion and low coupling, often thought of as technical metrics, can be applied to the concepts themselves. In a MODEL-DRIVEN DESIGN, MODULES are part of the model, and they should reflect concepts in the domain.
本章将所有这些构建模块整合在一起,这些模块体现了软件中的模型。这些想法是传统的,而由此产生的建模和设计偏见之前已经有人写过。但在这个背景下构建它们将有助于开发人员创建详细的组件,这些组件将在解决更大的模型和设计问题时满足领域驱动设计的优先事项。此外,了解基本原则将有助于开发人员在不可避免的妥协中保持正轨。
This chapter brings together all of these building blocks, which embody the model in software. These ideas are conventional, and the modeling and design biases that follow from them have been written about before. But framing them in this context will help developers create detailed components that will serve the priorities of domain-driven design when tackling the larger model and design issues. Also, a sense of the basic principles will help developers stay on course through the inevitable compromises.
建模和实现之间的交互由于对象之间的关联而特别棘手。
The interaction between modeling and implementation is particularly tricky with the associations between objects.
对于模型中每个可遍历的关联,软件中都有一个具有相同属性的机制。
For every traversable association in the model, there is a mechanism in the software with the same properties.
显示客户与销售代表之间关联的模型对应两件事。一方面,它抽象了开发人员认为两个真实人物之间相关的关系。另一方面,它对应于两个 Java 对象之间的对象指针,或数据库查找的封装,或某种类似的实现。
A model that shows an association between a customer and a sales representative corresponds to two things. On one hand, it abstracts a relationship developers deemed relevant between two real people. On the other hand, it corresponds to an object pointer between two Java objects, or an encapsulation of a database lookup, or some comparable implementation.
例如,一对多关联可能被实现为实例变量中的集合。但设计不一定如此直接。可能没有集合;访问器方法可能会查询数据库以查找适当的记录并根据它们实例化对象。这两种设计都反映了相同的模型。设计必须指定一种特定的遍历机制,其行为与模型中的关联一致。
For example, a one-to-many association might be implemented as a collection in an instance variable. But the design is not necessarily so direct. There may be no collection; an accessor method may query a database to find the appropriate records and instantiate objects based on them. Both of these designs would reflect the same model. The design has to specify a particular traversal mechanism whose behavior is consistent with the association in the model.
在现实生活中,存在许多多对多关联,而且很多关联都是双向的。当我们集思广益并探索领域时,模型的早期形式往往也是如此。但这些一般关联使实施和维护变得复杂。此外,它们很少传达关系的性质。
In real life, there are lots of many-to-many associations, and a great number are naturally bidirectional. The same tends to be true of early forms of a model as we brainstorm and explore the domain. But these general associations complicate implementation and maintenance. Furthermore, they communicate very little about the nature of the relationship.
至少有三种方法可以使关联更易于处理。
There are at least three ways of making associations more tractable.
1.施加遍历方向
1. Imposing a traversal direction
2.添加限定符,有效减少多重性
2. Adding a qualifier, effectively reducing multiplicity
3.消除不必要的关联
3. Eliminating nonessential associations
尽可能地约束关系非常重要。双向关联意味着只有同时理解两个对象才能理解。当应用程序需求不需要双向遍历时,添加遍历方向可以减少相互依赖并简化设计。了解领域可能会揭示自然的方向性偏差。
It is important to constrain relationships as much as possible. A bidirectional association means that both objects can be understood only together. When application requirements do not call for traversal in both directions, adding a traversal direction reduces interdependence and simplifies the design. Understanding the domain may reveal a natural directional bias.
美国有过很多位总统,其他国家也一样。这是一种双向的一对多关系。然而,我们很少会从“乔治·华盛顿”这个名字开始问:“他是哪个国家的总统?”从实用主义的角度来说,我们可以将这种关系简化为单向关联,从国家到总统。这种细化实际上反映了对领域,并做出更实用的设计。它体现了这样的理解:关联的一个方向比另一个方向更有意义、更重要。它使“Person”类独立于远不那么重要的“President”概念。
The United States has had many presidents, as have many other countries. This is a bidirectional, one-to-many relationship. Yet we seldom would start out with the name “George Washington” and ask, “Of which country was he president?” Pragmatically, we can reduce the relationship to a unidirectional association, traversable from country to president. This refinement actually reflects insight into the domain, as well as making a more practical design. It captures the understanding that one direction of the association is much more meaningful and important than the other. It keeps the “Person” class independent of the far less fundamental concept of “President.”
图 5.1。一些遍历方向反映了领域中的自然偏见。
Figure 5.1. Some traversal directions reflect a natural bias in the domain.
很多时候,更深入的理解会导致“限定”关系。深入研究总统,我们会发现(也许除了内战)一个国家一次只能有一位总统。这个限定词将多重性降低到一对一,并明确地将一条重要规则嵌入模型中。1790 年的美国总统是谁?乔治·华盛顿。
Very often, deeper understanding leads to a “qualified” relationship. Looking deeper into presidents, we realize that (except in a civil war, perhaps) a country has only one president at a time. This qualifier reduces the multiplicity to one-to-one, and explicitly embeds an important rule into the model. Who was president of the United States in 1790? George Washington.
图 5.2。受限关联传达了更多的知识,是更实用的设计。
Figure 5.2. Constrained associations communicate more knowledge and are more practical designs.
限制多对多关联的遍历方向可有效地将其实现简化为一对多——这是一种更简单的设计。
Constraining the traversal direction of a many-to-many association effectively reduces its implementation to one-to-many—a much easier design.
以反映领域偏好的方式一致地约束关联不仅使这些关联更具沟通性且更易于实现,而且还赋予剩余的双向关联以重要意义。当关系的双向性是领域的语义特征时,当应用程序功能需要它时,保留两个遍历方向就传达了这一点。
Consistently constraining associations in ways that reflect the bias of the domain not only makes those associations more communicative and simpler to implement, it also gives significance to the remaining bidirectional associations. When the bidirectionality of a relationship is a semantic characteristic of the domain, when it’s needed for application functionality, the retention of both traversal directions conveys that.
当然,如果关联对于手头的工作或模型对象的基本含义不是必不可少的,那么最终的简化就是完全消除关联。
Of course, the ultimate simplification is to eliminate an association altogether, if it is not essential to the job at hand or the fundamental meaning of the model objects.
图 5.3
Figure 5.3
此模型中的经纪账户的一个 Java 实现是
One Java implementation of Brokerage Account in this model would be
public class BrokerageAccount {
String accountNumber;
Customer customer;
Set investments;
// 省略构造函数等
public Customer getCustomer() {
return customer;
}
public Set getInvestments() {
return investments;
}
}
public class BrokerageAccount {
String accountNumber;
Customer customer;
Set investments;
// Constructors, etc. omitted
public Customer getCustomer() {
return customer;
}
public Set getInvestments() {
return investments;
}
}
但是如果我们需要从关系数据库中获取数据,那么与模型同样一致的另一种实现方式如下:
But if we need to fetch the data from a relational database, another implementation, equally consistent with the model, would be the following:
表:BROKERAGE_ACCOUNT
Table: BROKERAGE_ACCOUNT
表格:客户
Table: CUSTOMER
表:投资
Table: INVESTMENT
public class BrokerageAccount {
String accountNumber;
String customerSocialSecurityNumber;
// 省略构造函数等。
public Customer getCustomer() {
String sqlQuery =
"SELECT * FROM CUSTOMER WHERE" +
"SS_NUMBER='"+customerSocialSecurityNumber+"'";
return QueryService.findSingleCustomerFor(sqlQuery);
}
public Set getInvestments() {
String sqlQuery =
"SELECT * FROM INVESTMENT WHERE" +
"BROKERAGE_ACCOUNT='"+accountNumber+"'";
return QueryService.findInvestmentsFor(sqlQuery);
}
}
public class BrokerageAccount {
String accountNumber;
String customerSocialSecurityNumber;
// Omit constructors, etc.
public Customer getCustomer() {
String sqlQuery =
"SELECT * FROM CUSTOMER WHERE" +
"SS_NUMBER='"+customerSocialSecurityNumber+"'";
return QueryService.findSingleCustomerFor(sqlQuery);
}
public Set getInvestments() {
String sqlQuery =
"SELECT * FROM INVESTMENT WHERE" +
"BROKERAGE_ACCOUNT='"+accountNumber+"'";
return QueryService.findInvestmentsFor(sqlQuery);
}
}
(注意: QueryService 是一个用于从数据库中提取行并创建对象的实用程序,它对于解释示例来说很简单,但对于实际项目来说不一定是一个好的设计。)
(Note: The QueryService, a utility for fetching rows from the database and creating objects, is simple for explaining examples, but it’s not necessarily a good design for a real project.)
让我们通过限定经纪账户和投资之间的关联来完善模型,减少其多重性。这意味着每只股票只能有一项投资。
Let’s refine the model by qualifying the association between Brokerage Account and Investment, reducing its multiplicity. This says there can be only one investment per stock.
图 5.4
Figure 5.4
并非所有业务情况都是如此(例如,如果需要跟踪批次),但无论具体规则如何,在发现关联约束时,它们都应包含在模型和实施中。它们使模型更精确,实施更易于维护。
This wouldn’t be true of all business situations (for example, if the lots need to be tracked), but whatever the particular rules, as constraints on associations are discovered they should be included in the model and implementation. They make the model more precise and the implementation easier to maintain.
Java 实现可能变成:
The Java implementation could become:
public class BrokerageAccount {
String accountNumber;
Customer customer;
Map investments;
// 省略构造函数等。
public Customer getCustomer() {
return customer;
}
public Investment getInvestment(String stockSymbol) {
return (Investment)investments.get(stockSymbol);
}
}
public class BrokerageAccount {
String accountNumber;
Customer customer;
Map investments;
// Omitting constructors, etc.
public Customer getCustomer() {
return customer;
}
public Investment getInvestment(String stockSymbol) {
return (Investment)investments.get(stockSymbol);
}
}
基于 SQL 的实现如下:
And an SQL-based implementation would be:
public class BrokerageAccount {
String accountNumber;
String customerSocialSecurityNumber;
//省略构造函数等
public Customer getCustomer() {
String sqlQuery = "SELECT * FROM CUSTOMER WHERE SS_NUMBER='"
+ customerSocialSecurityNumber + "'";
return QueryService.findSingleCustomerFor(sqlQuery);
}
public Investment getInvestment(String stockSymbol) {
String sqlQuery = "SELECT * FROM INVESTMENT "
+ "WHERE BROKERAGE_ACCOUNT='" + accountNumber + "'"
+ "AND STOCK_SYMBOL='" + stockSymbol +"'";
return QueryService.findInvestmentFor(sqlQuery);
}
}
public class BrokerageAccount {
String accountNumber;
String customerSocialSecurityNumber;
//Omitting constructors, etc.
public Customer getCustomer() {
String sqlQuery = "SELECT * FROM CUSTOMER WHERE SS_NUMBER='"
+ customerSocialSecurityNumber + "'";
return QueryService.findSingleCustomerFor(sqlQuery);
}
public Investment getInvestment(String stockSymbol) {
String sqlQuery = "SELECT * FROM INVESTMENT "
+ "WHERE BROKERAGE_ACCOUNT='" + accountNumber + "'"
+ "AND STOCK_SYMBOL='" + stockSymbol +"'";
return QueryService.findInvestmentFor(sqlQuery);
}
}
仔细提炼和约束模型的关联将使您在模型驱动设计方面取得长足进步。现在让我们转向对象本身。某些区别可以阐明模型,同时实现更实际的实现。...
Carefully distilling and constraining the model’s associations will take you a long way toward a MODEL-DRIVEN DESIGN. Now let’s turn to the objects themselves. Certain distinctions clarify the model while making for a more practical implementation. . . .
许多物体从根本上来说不是由其属性来定义的,而是由连续性和同一性的线索来定义的。
Many objects are not fundamentally defined by their attributes, but rather by a thread of continuity and identity.
一位女房东起诉我,声称她的房产遭到严重损坏。我收到的文件描述了一间公寓,墙上有洞,地毯上有污渍,水槽里有有毒液体,散发出腐蚀性气体,导致厨房墙纸脱落。法庭文件称我是造成损失的房客,并指明了我的姓名和我当时的住址。这让我很困惑,因为我从来没有去过那个被毁坏的地方。
A landlady sued me, claiming major damages to her property. The papers I was served described an apartment with holes in the walls, stains on the carpet, and a noxious liquid in the sink that gave off caustic fumes that had made the kitchen wallpaper peel. The court documents named me as the tenant responsible for the damages, identifying me by name and by my then-current address. This was confusing to me, because I had never even visited that ruined place.
过了一会儿,我意识到这肯定是一起误认身份的案件。我打电话给原告,告诉她这件事,但她不相信我。前房客已经躲了她好几个月了。我怎么能证明我不是那个花了她那么多钱的人呢?我是电话簿上唯一的埃里克·埃文斯。
After a moment, I realized that it must be a case of mistaken identity. I called the plaintiff and told her this, but she didn’t believe me. The former tenant had been eluding her for months. How could I prove that I was not the same person who had cost her so much money? I was the only Eric Evans in the phone book.
好吧,电话簿拯救了我。因为我已经在同一间公寓里住了两年,所以我问她是否还留着去年的电话簿。在她找到电话簿并确认我的名单相同(就在我同名者的名单旁边)后,她意识到我不是她想要起诉的人,向我道了歉,并承诺撤销此案。
Well, the phone book turned out to be my salvation. Because I had been living in the same apartment for two years, I asked her if she still had the previous year’s book. After she found it and verified that my listing was the same (right next to my namesake’s listing), she realized that I was not the person she wanted to sue, apologized, and promised to drop the case.
计算机并不是那么灵活。软件系统中一旦出现错误识别,就会导致数据损坏和程序错误。
Computers are not that resourceful. A case of mistaken identity in a software system leads to data corruption and program errors.
这里有一些特殊的技术挑战,稍后我会讨论,但首先让我们看看根本问题:许多事物都是由其身份而不是任何属性定义的。在我们的典型概念中,一个人(继续使用非技术示例)的身份从出生到死亡甚至更久。该人的身体属性会发生变化并最终消失。名字可能会改变。财务关系来来去去。没有一个人的任何一个属性是不能改变的;但身份仍然存在。我还是五岁时的我吗?这种形而上学的问题在寻找有效的领域模型时很重要。稍微改述一下:应用程序的用户会在意我是否还是五岁时的我吗?
There are special technical challenges here, which I’ll discuss in a bit, but first let’s look at the fundamental issue: Many things are defined by their identity, and not by any attribute. In our typical conception, a person (to continue with the nontechnical example) has an identity that stretches from birth to death and even beyond. That person’s physical attributes transform and ultimately disappear. The name may change. Financial relationships come and go. There is not a single attribute of a person that cannot change; yet the identity persists. Am I the same person I was at age five? This kind of metaphysical question is important in the search for effective domain models. Slightly rephrased: Does the user of the application care if I am the same person I was at age five?
在跟踪应付账款的软件系统中,那个不起眼的“客户”对象可能有更丰富多彩的一面。它通过及时付款积累状态,或因未付款而被移交给收账机构。当销售人员将客户数据提取到其联系人管理软件中时,它可能在另一个系统中过着双重生活。无论如何,它都会被毫不客气地压缩成扁平状,存储在数据库表中。当新业务不再从该来源流出时,客户对象将被退役到档案库中,成为其昔日自我的影子。
In a software system for tracking accounts due, that modest “customer” object may have a more colorful side. It accumulates status by prompt payment or is turned over to a bill-collection agency for failure to pay. It may lead a double life in another system altogether when the sales force extracts customer data into its contact management software. In any case, it is unceremoniously squashed flat to be stored in a database table. When new business stops flowing from that source, the customer object will be retired to an archive, a shadow of its former self.
每种形式的客户都是基于不同的编程语言和技术而实现的。但是,当接到订单电话时,重要的是要知道:这是拖欠账款的客户吗?这是杰克(某位销售代表)已经合作了数周的客户吗?这是一位全新的客户吗?
Each of these forms of the customer is a different implementation based on a different programming language and technology. But when a phone call comes in with an order, it is important to know: Is this the customer who has the delinquent account? Is this the customer that Jack (a particular sales representative) has been working with for weeks? Is this a completely new customer?
概念身份必须在对象的多个实现、其存储形式和现实世界参与者(例如电话呼叫者)之间进行匹配。属性可能不匹配。销售代表可能已将地址更新输入到联系人软件中,该更新正被传播到应付账户。两个客户联系人可能有相同的名字。在分布式软件中,多个用户可以输入来自不同来源的数据,从而导致更新事务通过系统传播,并在不同的数据库中异步进行协调。
A conceptual identity has to be matched between multiple implementations of the objects, its stored forms, and real-world actors such as the phone caller. Attributes may not match. A sales representative may have entered an address update into the contact software, which is just being propagated to accounts due. Two customer contacts may have the same name. In distributed software, multiple users could be entering data from different sources, causing update transactions to propagate through the system to be reconciled in different databases asynchronously.
对象建模倾向于让我们关注对象的属性,但实体(ENTITY)的基本概念是贯穿生命周期甚至多种形式的抽象连续性。
Object modeling tends to lead us to focus on the attributes of an object, but the fundamental concept of an ENTITY is an abstract continuity threading through a life cycle and even passing through multiple forms.
有些对象并非主要由其属性定义。它们代表着贯穿时间且通常跨越不同表示的身份线索。有时,即使属性不同,这样的对象也必须与另一个对象匹配。即使一个对象可能具有相同的属性,也必须将其与其他对象区分开来。错误的身份可能会导致数据损坏。
Some objects are not defined primarily by their attributes. They represent a thread of identity that runs through time and often across distinct representations. Sometimes such an object must be matched with another object even though attributes differ. An object must be distinguished from other objects even though they might have the same attributes. Mistaken identity can lead to data corruption.
主要通过其身份定义的对象称为实体。1实体具有特殊的建模和设计考虑。它们的生命周期可以彻底改变其形式和内容,但必须保持连续性。必须定义它们的身份,以便有效地跟踪它们。它们的类定义、职责、属性和关联应该围绕它们是谁,而不是它们所具有的特定属性。即使对于不会如此彻底地改变或具有如此复杂的生命周期的实体,将它们放在语义类别中也会产生更清晰的模型和更强大的实现。
An object defined primarily by its identity is called an ENTITY.1 ENTITIES have special modeling and design considerations. They have life cycles that can radically change their form and content, but a thread of continuity must be maintained. Their identities must be defined so that they can be effectively tracked. Their class definitions, responsibilities, attributes, and associations should revolve around who they are, rather than the particular attributes they carry. Even for ENTITIES that don’t transform so radically or have such complicated life cycles, placing them in the semantic category leads to more lucid models and more robust implementations.
当然,软件系统中的大多数“实体”都不是通常意义上的人或实体。实体是指在生命周期中具有连续性并且与应用程序用户重要的属性无关的任何事物。它可以是人、城市、汽车、彩票或银行交易。
Of course, most “ENTITIES” in a software system are not people or entities in the usual sense of the word. An ENTITY is anything that has continuity through a life cycle and distinctions independent of attributes that are important to the application’s user. It could be a person, a city, a car, a lottery ticket, or a bank transaction.
另一方面,模型中的对象并非都是具有有意义身份的实体。面向对象语言将“身份”操作构建到每个对象中(例如,==Java 中的“ ”运算符),这一事实使这个问题变得复杂。这些操作通过比较两个引用在内存中的位置或其他机制来确定它们是否指向同一个对象。从这个意义上讲,每个对象实例都有身份。在创建 Java 运行时环境或用于本地缓存远程对象的技术框架的领域中,每个对象实例可能确实是一个实体。但这种身份机制在其他应用领域意义不大。身份是实体的一个微妙而有意义的属性,不能归结为语言的自动特性。
On the other hand, not all objects in the model are ENTITIES, with meaningful identities. This issue is confused by the fact that object-oriented languages build “identity” operations into every object (for example, the “==” operator in Java). These operations determine if two references point to the same object by comparing their location in memory or by some other mechanism. In this sense, every object instance has identity. In the domain of, say, creating a Java runtime environment or a technical framework for caching remote objects locally, every object instance may indeed be an ENTITY. But this identity mechanism means very little in other application domains. Identity is a subtle and meaningful attribute of ENTITIES, which can’t be turned over to the automatic features of the language.
考虑银行应用程序中的交易。同一天向同一帐户存入两笔相同金额的款项仍然是不同的交易,因此它们具有身份并且是ENTITIES。另一方面,这两笔交易的金额属性可能是某个货币对象的实例。这些值没有身份,因为区分它们没有任何用处。事实上,两个对象可以具有相同的身份,而无需具有相同的属性,甚至不必属于同一类。当银行客户将银行对账单的交易与支票登记簿的交易进行核对时,具体来说,任务是匹配具有相同身份的交易,即使它们是由不同的人在不同的日期记录的(银行结算日期晚于支票上的日期)。支票号码的目的是作为此目的的唯一标识符,无论问题是由计算机程序还是手动处理。没有识别号的存款和现金取款可能更棘手,但同样的原则适用:每笔交易都是一个ENTITY,它至少以两种形式出现。
Consider transactions in a banking application. Two deposits of the same amount to the same account on the same day are still distinct transactions, so they have identity and are ENTITIES. On the other hand, the amount attributes of those two transactions are probably instances of some money object. These values have no identity, since there is no usefulness in distinguishing them. In fact, two objects can have the same identity without having the same attributes or even, necessarily, being of the same class. When the bank customer is reconciling the transactions of the bank statement with the transactions of the check registry, the task is, specifically, to match transactions that have the same identity, even though they were recorded by different people on different dates (the bank clearing date being later than the date on the check). The purpose of the check number is to serve as a unique identifier for this purpose, whether the problem is being handled by a computer program or by hand. Deposits and cash withdrawals, which don’t have an identifying number, can be trickier, but the same principle applies: each transaction is an ENTITY, which appears in at least two forms.
身份在特定软件系统之外通常很重要,例如银行交易和公寓租户。但有时身份仅在系统环境中才重要,例如计算机进程的身份。
It is common for identity to be significant outside a particular software system, as is the case with the banking transactions and the apartment tenants. But sometimes the identity is important only in the context of the system, such as the identity of a computer process.
所以:
Therefore:
当对象通过其身份而不是属性来区分时,应将此作为其在模型中的定义的主要内容。保持类定义简单并专注于生命周期的连续性和身份。定义一种区分每个对象的方法,无论其形式或历史如何。注意要求通过属性匹配对象的需求。定义一种保证为每个对象产生唯一结果的操作,可能通过附加一个保证唯一的符号来实现。这种识别方法可能来自外部,也可能是由系统创建的任意标识符,但它必须与模型中的身份区别相对应。模型必须定义 同一事物的含义。
When an object is distinguished by its identity, rather than its attributes, make this primary to its definition in the model. Keep the class definition simple and focused on life cycle continuity and identity. Define a means of distinguishing each object regardless of its form or history. Be alert to requirements that call for matching objects by attributes. Define an operation that is guaranteed to produce a unique result for each object, possibly by attaching a symbol that is guaranteed unique. This means of identification may come from the outside, or it may be an arbitrary identifier created by and for the system, but it must correspond to the identity distinctions in the model. The model must define what it means to be the same thing.
身份不是世界上某事物的固有属性;它是一种因为有用而附加的意义。事实上,同一个现实世界的事物在领域模型中可能会也可能不会表示为实体。
Identity is not intrinsic to a thing in the world; it is a meaning superimposed because it is useful. In fact, the same real-world thing might or might not be represented as an ENTITY in a domain model.
体育场座位预订应用程序可能会将座位和观众视为实体。在指定座位的情况下,每张票上都有一个座位号,座位就是一个实体。其标识符是座位号,该号码在体育场内是唯一的。座位可能还有许多其他属性,例如其位置、视线是否受阻以及价格,但只有座位号或唯一的排和位置可用于标识和区分座位。
An application for booking seats in a stadium might treat seats and attendees as ENTITIES. In the case of assigned seating, in which each ticket has a seat number on it, the seat is an ENTITY. Its identifier is the seat number, which is unique within the stadium. The seat may have many other attributes, such as its location, whether the view is obstructed, and the price, but only the seat number, or a unique row and position, is used to identify and distinguish seats.
另一方面,如果活动是“普通入场”,即持票人可以坐在任何空座位上,则无需区分各个座位。只有座位总数才重要。虽然座位号仍刻在实际座位上,但软件无需跟踪它们。事实上,模型将特定座位号与门票关联起来是错误的,因为在普通入场活动中没有这样的限制。在这种情况下,座位不是实体,也不需要 标识符。
On the other hand, if the event is “general admission,” meaning that ticket holders sit wherever they find an empty seat, there is no need to distinguish individual seats. Only the total number of seats is important. Although the seat numbers are still engraved on the physical seats, there is no need for the software to track them. In fact, it would be erroneous for the model to associate specific seat numbers with tickets, because there is no such constraint at a general admission event. In such a case, seats are not ENTITIES, and no identifier is needed.
在对对象进行建模时,考虑属性是很自然的,而考虑其行为也相当重要。但ENTITIES的最基本职责是建立连续性,以使行为清晰且可预测。如果保持空闲,它们可以发挥最大的作用。不要关注属性甚至行为,而是将ENTITY对象的定义剥离到最内在的特征,特别是那些可识别它或常用于查找或匹配它的特征。只添加对概念至关重要的行为以及该行为所需的属性。除此之外,尝试将行为和属性移除到与核心 ENTITY 关联的其他对象中。其中一些将是其他ENTITIES。一些将是VALUE OBJECTS,这是本章中的下一个模式。除了身份问题之外,ENTITIES倾向于通过协调其拥有的对象的操作来履行其职责。
It is natural to think about the attributes when modeling an object, and it is quite important to think about its behavior. But the most basic responsibility of ENTITIES is to establish continuity so that behavior can be clear and predictable. They do this best if they are kept spare. Rather than focusing on the attributes or even the behavior, strip the ENTITY object’s definition down to the most intrinsic characteristics, particularly those that identify it or are commonly used to find or match it. Add only behavior that is essential to the concept and attributes that are required by that behavior. Beyond that, look to remove behavior and attributes into other objects associated with the core ENTITY. Some of these will be other ENTITIES. Some will be VALUE OBJECTS, which is the next pattern in this chapter. Beyond identity issues, ENTITIES tend to fulfill their responsibilities by coordinating the operations of objects they own.
customerID 是图 5.5中Customer ENTITY的唯一标识符,但电话号码和地址通常用于查找或匹配Customer。姓名不定义一个人的身份,但通常用作确定身份的手段的一部分。在此示例中,电话和地址属性移至Customer,但在实际项目中,该选择取决于通常如何匹配或区分域的客户。例如,如果 Customer有多个用于不同目的的联系电话号码,则电话号码与身份无关,应保留在Sales Contact中。
The customerID is the one and only identifier of the Customer ENTITY in Figure 5.5, but the phone number and address would often be used to find or match a Customer. The name does not define a person’s identity, but it is often used as part of the means of determining it. In this example, the phone and address attributes moved into Customer, but on a real project, that choice would depend on how the domain’s customers are typically matched or distinguished. For example, if a Customer has many contact phone numbers for different purposes, then the phone number is not associated with identity and should stay with the Sales Contact.
图 5.5。与身份相关的属性保留在实体中。
Figure 5.5. Attributes associated with identity stay with the ENTITY.
每个实体必须有一种可操作的方式来与其他对象建立身份 — 即使是与具有相同描述属性的其他对象也可以区分。无论系统如何定义,识别属性都必须保证在系统内是唯一的 — 即使是分布式的,即使是对象已归档。
Each ENTITY must have an operational way of establishing its identity with another object—distinguishable even from another object with the same descriptive attributes. An identifying attribute must be guaranteed to be unique within the system however that system is defined—even if distributed, even when objects are archived.
如前所述,面向对象语言具有“身份”操作,通过比较对象在内存中的位置来确定两个引用是否指向同一个对象。这种身份跟踪对于我们的目的来说太脆弱了。在大多数用于持久存储对象的技术中,每次从数据库中检索对象时,都会创建一个新实例,因此初始身份会丢失。每次通过网络传输对象时,都会在目的地创建一个新实例,身份再次丢失。当系统中存在同一对象的多个版本时,问题可能会更加严重,例如当更新通过分布式数据库传播时。
As mentioned earlier, object-oriented languages have “identity” operations that determine if two references point to the same object by comparing the objects’ locations in memory. This kind of identity tracking is too fragile for our purposes. In most technologies for persistent storage of objects, every time an object is retrieved from a database, a new instance is created, and so the initial identity is lost. Every time an object is transmitted across a network, a new instance is created on the destination, and once again the identity is lost. The problem can be even worse when multiple versions of the same object exist in the system, such as when updates propagate through a distributed database.
即使有简化这些技术问题的框架,基本问题仍然存在:如何知道两个对象代表相同的概念实体?身份的定义来自模型。定义身份需要了解领域。
Even with frameworks that simplify these technical problems, the fundamental issue exists: How do you know that two objects represent the same conceptual ENTITY? The definition of identity emerges from the model. Defining identity demands understanding of the domain.
有时,某些数据属性或属性组合可以保证或简单地限制为在系统内是唯一的。这种方法为 ENTITY 提供了唯一的键。例如,日报可以通过报纸名称、城市和出版日期来识别。(但要注意额外的版本和名称更改!)
Sometimes certain data attributes, or combinations of attributes, can be guaranteed or simply constrained to be unique within the system. This approach provides a unique key for the ENTITY. Daily newspapers, for example, might be identified by the name of the newspaper, the city, and the date of publication. (But watch out for extra editions and name changes!)
当没有由对象属性组成的真正唯一键时,另一种常见解决方案是将类内唯一的符号(如数字或字符串)附加到每个实例。一旦创建此 ID 符号并将其存储为 ENTITY 的属性,它就被指定为不可变的。它绝不能改变,即使开发系统无法直接执行此规则。例如,当对象被平铺到数据库并重建时,ID 属性将被保留。有时技术框架有助于此过程,但除此之外,它只需要工程学科。
When there is no true unique key made up of the attributes of an object, another common solution is to attach to each instance a symbol (such as a number or a string) that is unique within the class. Once this ID symbol is created and stored as an attribute of the ENTITY, it is designated immutable. It must never change, even if the development system is unable to directly enforce this rule. For example, the ID attribute is preserved as the object gets flattened into a database and reconstructed. Sometimes a technical framework helps with this process, but otherwise it just takes engineering discipline.
ID 通常由系统自动生成。生成算法必须保证系统内的唯一性,这对于并发处理和分布式系统来说可能是一个挑战。生成这样的 ID 可能需要超出本书范围的技术。这里的目标是指出何时会出现这些注意事项,以便开发人员意识到他们有一个需要解决的问题,并知道如何将他们的关注点缩小到关键领域。关键是要认识到身份问题取决于模型的特定方面。通常,识别方法也需要仔细研究领域。
Often the ID is generated automatically by the system. The generation algorithm must guarantee uniqueness within the system, which can be a challenge with concurrent processing and in distributed systems. Generating such an ID may require techniques that are beyond the scope of this book. The goal here is to point out when the considerations arise, so that developers are aware they have a problem to solve and know how to narrow down their concerns to the critical areas. The key is to recognize that identity concerns hinge on specific aspects of the model. Often, the means of identification demand a careful study of the domain, as well.
当 ID 自动生成时,用户可能永远不需要看到它。ID 可能仅在内部需要,例如在联系人管理应用程序中,该应用程序允许用户通过姓名查找记录。程序需要能够以简单、明确的方式区分两个名字完全相同的联系人。唯一的内部 ID 让系统能够做到这一点。在检索到两个不同的项目,系统将向用户显示两个单独的联系人,但可能不会显示 ID。用户将根据他们的公司、位置等来区分他们。
When the ID is automatically generated, the user may never need to see it. The ID may be needed only internally, such as in a contact management application that lets the user find records by a person’s name. The program needs to be able to distinguish two contacts with exactly the same name in a simple, unambiguous way. The unique, internal IDs let the system do just that. After retrieving the two distinct items, the system will show two separate contacts to the user, but the IDs may not be shown. The user will distinguish them on the basis of their company, their location, and so on.
最后,在某些情况下,生成的 ID会引起用户的兴趣。当我通过包裹递送服务寄送包裹时,我会得到一个由快递公司软件生成的跟踪号,我可以使用该跟踪号来识别和跟踪我的包裹。当我预订机票或预订酒店时,我会得到确认号,这是交易的唯一标识符。
Finally, there are cases in which a generated ID is of interest to the user. When I ship a package through a parcel delivery service, I’m given a tracking number, generated by the shipping company’s software, which I can use to identify and follow up on my package. When I book airline tickets or reserve a hotel, I’m given confirmation numbers that are unique identifiers for the transaction.
在某些情况下,ID 的唯一性必须超越计算机系统的界限。例如,如果两家拥有独立计算机系统的医院之间交换医疗记录,理想情况下,每个系统将使用相同的患者 ID,但如果它们生成自己的符号,则很难做到这一点。此类系统通常使用其他机构(通常是政府机构)颁发的标识符。在美国,医院通常使用社会安全号码作为个人标识符。这种方法并非万无一失。并非每个人都有社会安全号码(尤其是儿童和非美国居民),而且出于隐私原因,许多人反对使用它。
In some cases, the uniqueness of the ID must apply beyond the computer system’s boundaries. For example, if medical records are being exchanged between two hospitals that have separate computer systems, ideally each system will use the same patient ID, but this is difficult if they generate their own symbol. Such systems often use an identifier issued by some other institution, typically a government agency. In the United States, the Social Security number is often used by hospitals as an identifier for a person. Such methods are not foolproof. Not everyone has a Social Security number (children and nonresidents of the United States, especially), and many people object to its use, for privacy reasons.
在不太正式的场合(比如,视频租赁),电话号码被用作标识符。但一部电话可以共用。号码可以更改。旧号码甚至可以重新分配给另一个人。
In less formal situations (say, video rental), telephone numbers are used as identifiers. But a telephone can be shared. The number can change. An old number can even be reassigned to a different person.
出于这些原因,通常会使用专门分配的标识符(例如常旅客号码),并使用其他属性(例如电话号码和社会安全号码)进行匹配和验证。无论如何,当应用程序需要外部 ID 时,系统用户有责任提供唯一的 ID,并且系统必须为他们提供足够的工具来处理出现的异常情况。
For these reasons, specially assigned identifiers are often used (such as frequent flier numbers), and other attributes, such as phone numbers and Social Security numbers, are used to match and verify. In any case, when the application requires an external ID, the users of the system become responsible for supplying IDs that are unique, and the system must give them adequate tools to handle exceptions that arise.
考虑到所有这些技术问题,人们很容易忽视潜在的概念问题:两个对象是同一事物意味着什么?为每个对象标记一个 ID,或者编写一个比较两个实例的操作是相当容易的,但如果这些 ID 或操作与领域中的一些有意义的区别不符,它们只会让事情更加混乱。这就是为什么身份分配操作通常需要人工输入的原因。例如,支票簿对账软件可能会提供可能的匹配,但最终的决定权还是在用户手中。
Given all these technical problems, it is easy to lose sight of the underlying conceptual problem: What does it mean for two objects to be the same thing? It is easy enough to stamp each object with an ID, or to write an operation that compares two instances, but if these IDs or operations don’t correspond to some meaningful distinction in the domain, they just confuse matters more. This is why identity-assigning operations often involve human input. Checkbook reconciliation software, for instance, may offer likely matches, but the user is expected to make the final determination.
Many objects have no conceptual identity. These objects describe some characteristic of a thing.
当孩子在画画时,他会在意自己选择的记号笔的颜色,也可能在意笔尖的锋利程度。但如果有两支颜色和形状相同的记号笔,他可能不会在意自己用哪一支。如果一支记号笔丢失了,可以用一包新买的同色记号笔替换,他可以继续画画,不用担心更换。
When a child is drawing, he cares about the color of the marker he chooses, and he may care about the sharpness of the tip. But if there are two markers of the same color and shape, he probably won’t care which one he uses. If a marker is lost and replaced by another of the same color from a new pack, he can resume his work unconcerned about the switch.
询问孩子冰箱上的各种图画,他很快就能分辨出自己画的和姐姐画的。他和姐姐的身份很有用,他们完成的画也很有用。但想象一下,如果他必须追踪画中的哪些线条是由每只记号笔画的,那将是多么复杂。绘画将不再是小孩子的游戏。
Ask the child about the various drawings on the refrigerator, and he will quickly distinguish those he made from those his sister made. He and his sister have useful identities, as do their completed drawings. But imagine how complicated it would be if he had to track which lines in a drawing were made by each marker. Drawing would no longer be child’s play.
因为模型中最显眼的对象通常是ENTITIES,而且追踪每个ENTITY的身份非常重要,所以很自然地会考虑为所有域对象分配一个身份。事实上,一些框架会为每个对象分配一个唯一的 ID。
Because the most conspicuous objects in a model are usually ENTITIES, and because it is so important to track each ENTITY’s identity, it is natural to consider assigning an identity to all domain objects. Indeed, some frameworks assign a unique ID to every object.
系统必须应对所有这些跟踪,许多可能的性能优化都被排除在外。需要进行分析工作来定义有意义的身份,并找出万无一失的跟踪方法对象可以跨分布式系统或数据库存储。同样重要的是,采用人为的身份会产生误导。它使模型混乱,迫使所有对象都陷入同一个模型中。
The system has to cope with all that tracking, and many possible performance optimizations are ruled out. Analytical effort is required to define meaningful identities and work out foolproof ways to track objects across distributed systems or in database storage. Equally important, taking on artificial identities is misleading. It muddles the model, forcing all objects into the same mold.
跟踪实体的身份至关重要,但将身份附加到其他对象会损害系统性能、增加分析工作,并使所有对象看起来都一样,从而使模型混乱。
Tracking the identity of ENTITIES is essential, but attaching identity to other objects can hurt system performance, add analytical work, and muddle the model by making all objects look the same.
软件设计是一场与复杂性的持续斗争。我们必须加以区分,以便只在必要时应用特殊处理。
Software design is a constant battle with complexity. We must make distinctions so that special handling is applied only where necessary.
但是,如果我们将这一类对象视为缺乏身份的对象,那么我们的工具箱或词汇量并没有增加多少。事实上,这些对象具有自己的特征,对模型具有自己的意义。 这些是描述事物的对象。
However, if we think of this category of object as just the absence of identity, we haven’t added much to our toolbox or vocabulary. In fact, these objects have characteristics of their own and their own significance to the model. These are the objects that describe things.
表示领域描述方面但没有概念标识的对象称为值对象。值对象被实例化以表示设计元素,我们只关心它们是什么,而不是它们是谁或哪个。
An object that represents a descriptive aspect of the domain with no conceptual identity is called a VALUE OBJECT. VALUE OBJECTS are instantiated to represent elements of the design that we care about only for what they are, not who or which they are.
颜色是许多现代开发系统的基础库中提供的VALUE OBJECTS的一个示例;字符串和数字也是如此。(您不关心您有哪个“4”或哪个“Q”。)这些基本示例很简单,但VALUE OBJECTS不一定简单。例如,颜色混合程序可能有一个丰富的模型,其中可以组合增强的颜色对象以产生其他颜色。这些颜色可能具有复杂的算法来协作以得出新的结果VALUE OBJECT。
Colors are an example of VALUE OBJECTS that are provided in the base libraries of many modern development systems; so are strings and numbers. (You don’t care which “4” you have or which “Q”.) These basic examples are simple, but VALUE OBJECTS are not necessarily simple. For example, a color-mixing program might have a rich model in which enhanced color objects could be combined to produce other colors. These colors could have complex algorithms for collaborating to derive the new resulting VALUE OBJECT.
值对象可以是其他对象的集合。在设计房屋平面图的软件中,可以为每种窗口样式创建一个对象。此“窗口样式”可以与高度和宽度以及控制如何更改和组合这些属性的规则一起合并到“窗口”对象中。这些窗口是由其他值对象组成的复杂值对象。它们反过来会被合并到平面图的更大元素中,例如“墙壁”对象。
A VALUE OBJECT can be an assemblage of other objects. In software for designing house plans, an object could be created for each window style. This “window style” could be incorporated into a “window” object, along with height and width, as well as rules governing how these attributes can be changed and combined. These windows are intricate VALUE OBJECTS made up of other VALUE OBJECTS. They in turn would be incorporated into larger elements of a plan, such as “wall” objects.
V ALUE OBJECTS甚至可以引用ENTITIES。例如,如果我向在线地图服务询问从旧金山到洛杉矶的风景优美的驾车路线,它可能会派生出一个通过太平洋海岸公路连接洛杉矶和旧金山的 Route 对象。该 Route 对象将是一个VALUE,即使它引用的三个对象(两个城市和一条高速公路)都是实体。
VALUE OBJECTS can even reference ENTITIES. For example, if I ask an online map service for a scenic driving route from San Francisco to Los Angeles, it might derive a Route object linking L.A. and San Francisco via the Pacific Coast Highway. That Route object would be a VALUE, even though the three objects it references (two cities and a highway) are all ENTITIES.
V ALUE OBJECTS经常作为参数在对象之间的消息中传递。它们通常是瞬态的,为操作创建然后被丢弃。V ALUE OBJECTS用作ENTITIES(和其他VALUES)的属性。一个人可能被建模为具有身份的ENTITY,但该人的名字是一个VALUE。
VALUE OBJECTS are often passed as parameters in messages between objects. They are frequently transient, created for an operation and then discarded. VALUE OBJECTS are used as attributes of ENTITIES (and other VALUES). A person may be modeled as an ENTITY with an identity, but that person’s name is a VALUE.
当您只关心模型元素的属性时,将其归类为VALUE OBJECT。使其表达其所传达的属性的含义并赋予其相关功能。将VALUE OBJECT视为不可变的。不要赋予它任何身份,并避免维护ENTITIES所需的设计复杂性。
When you care only about the attributes of an element of the model, classify it as a VALUE OBJECT. Make it express the meaning of the attributes it conveys and give it related functionality. Treat the VALUE OBJECT as immutable. Don’t give it any identity and avoid the design complexities necessary to maintain ENTITIES.
构成VALUE OBJECT的属性应形成一个概念整体。2例如,街道、城市和邮政编码不应是 Person 对象的单独属性。它们是单个完整地址的一部分,这使得 Person 更简单,VALUE OBJECT更连贯。
The attributes that make up a VALUE OBJECT should form a conceptual whole.2 For example, street, city, and postal code shouldn’t be separate attributes of a Person object. They are part of a single, whole address, which makes a simpler Person, and a more coherent VALUE OBJECT.
图 5.6。值对象 (VALUE OBJECT)可以给出关于实体 (ENTITY)的信息。它在概念上应该是完整的。
Figure 5.6. A VALUE OBJECT can give information about an ENTITY. It should be conceptually whole.
我们不关心我们拥有哪个VALUE OBJECT实例。这种无约束性为我们提供了设计自由,我们可以利用这些自由来简化设计或优化性能。这涉及对复制、共享和不变性的选择。
We don’t care which instance we have of a VALUE OBJECT. This lack of constraints gives us design freedom we can use to simplify the design or optimize performance. This involves making choices about copying, sharing, and immutability.
如果两个人的名字相同,这并不表示他们是同一个人,也不能表示他们可以互换。但表示名字的对象是可以互换的,因为只有名字的拼写才重要。Name 对象可以从第一个 Person 对象复制到第二个 Person 对象。
If two people have the same name, that does not make them the same person, or make them interchangeable. But the object representing the name is interchangeable, because only the spelling of the name matters. A Name object can be copied from the first Person object to the second.
事实上,两个 Person 对象可能不需要它们自己的名称实例。同一个 Name 对象可以在两个 Person 对象(每个对象都有一个指向相同名称实例的指针)之间共享,而不会改变它们的行为或身份。也就是说,它们的行为将是正确的,直到其中一个人的名字发生改变。然后另一个人的名字也会改变!为了防止这种情况,为了安全地共享对象,它必须是不可变的:除非完全替换,否则不能更改它。
In fact, the two Person objects might not need their own name instances. The same Name object could be shared between the two Person objects (each with a pointer to the same name instance) with no change in their behavior or identity. That is, their behavior will be correct until some change is made to the name of one person. Then the other person’s name would change also! To protect against this, in order for an object to be shared safely, it must be immutable: it cannot be changed except by full replacement.
当一个对象将其属性之一作为参数或返回值传递给另一个对象时,也会出现同样的问题。当游荡对象不受其所有者控制时,任何事情都可能发生。VALUE 可能会以破坏所有者的方式更改,即违反所有者的不变量。可以通过使传递的对象不可变或传递副本来避免此问题。
The same issues arise when an object passes one of its attributes to another object as an argument or return value. Anything could happen to the wandering object while it is out of control of its owner. The VALUE could be changed in a way that corrupts the owner, by violating the owner’s invariants. This problem is avoided either by making the passed object immutable, or by passing a copy.
创建额外的性能调优选项非常重要,因为VALUE OBJECTS往往数量众多。房屋设计软件的例子暗示了这一点。如果每个电源插座都是一个单独的VALUE OBJECTS,那么在一个房屋设计图的单个版本中可能会有上百个电源插座。但是,如果所有插座都被视为可互换的,我们可以共享一个插座实例并指向它一百次(FLYWEIGHT的示例[ Gamma et al. 1995 ])。在大型系统中,这种效果可以乘以数千,并且这样的优化可以使系统变得可用,而不是像爬行一样缓慢,被数百万个冗余对象所阻塞。这只是ENTITIES无法使用的优化技巧的一个例子。
Creating extra options for performance tuning can be important because VALUE OBJECTS tend to be numerous. The example of the house design software hints at this. If each electrical outlet is a separate VALUE OBJECT, there might be a hundred of them in a single version of a single house plan. But if all outlets are considered interchangeable, we could share just one instance of an outlet and point to it a hundred times (an example of FLYWEIGHT [Gamma et al. 1995]). In large systems, this kind of effect can be multiplied by thousands, and such an optimization can make the difference between a usable system and one that slows to a crawl, choked on millions of redundant objects. This is just one example of an optimization trick that is not available for ENTITIES.
复制与共享的经济性取决于实施环境。尽管复制可能会因大量对象而堵塞系统,但共享会减慢分布式系统的速度。当在两台机器之间传递副本时,会发送一条消息,副本独立存在于接收机器上。但如果共享单个实例,则只会传递一个引用,每次交互都需要向对象返回一条消息。
The economy of copying versus sharing depends on the implementation environment. Although copies may clog the system with huge numbers of objects, sharing can slow down a distributed system. When a copy is passed between two machines, a single message is sent and the copy lives independently on the receiving machine. But if a single instance is being shared, only a reference is passed, requiring a message back to the object for each interaction.
Sharing is best restricted to those cases in which it is most valuable and least troublesome:
• 当节省数据库中的空间或对象数量至关重要时
• When saving space or object count in the database is critical
• 当通信开销较低时(例如在集中式服务器中)
• When communication overhead is low (such as in a centralized server)
• 当共享对象严格不可变时
• When the shared object is strictly immutable
在某些语言和环境中可以声明属性或对象的不变性,但在其他语言和环境中则不能。这样的特性有助于传达设计决策,但并非必不可少。我们在模型中做出的许多区分无法使用大多数当前工具和编程语言在实现中明确声明。例如,您不能声明ENTITIES,然后自动执行身份操作。但缺乏对概念区别的直接语言支持并不意味着这种区别没有用。它只是意味着需要更多的纪律来维护在实现中隐含的规则。这可以通过命名约定、选择性文档和大量讨论来强化。
Immutability of an attribute or an object can be declared in some languages and environments but not in others. Such features help communicate the design decision, but they are not essential. Many of the distinctions we are making in the model cannot be explicitly declared in the implementation with most current tools and programming languages. You can’t declare ENTITIES, for example, and then have an identity operation automatically enforced. But the lack of direct language support for a conceptual distinction does not mean that the distinction is not useful. It just means that more discipline is needed to maintain the rules that will be only implicit in the implementation. This can be reinforced with naming conventions, selective documentation, and lots of discussion.
只要VALUE OBJECT是不可变的,变更管理就很简单 — 除了完全替换之外,没有任何变化。不可变对象可以自由共享,如电源插座示例。如果垃圾收集可靠,则删除只需删除对该对象的所有引用。当VALUE OBJECT在设计中被指定为不可变时,开发人员可以自由地在纯技术基础上做出有关复制和共享等问题的决策,并确保应用程序不依赖于对象的特定实例。
As long as a VALUE OBJECT is immutable, change management is simple—there isn’t any change except full replacement. Immutable objects can be freely shared, as in the electrical outlet example. If garbage collection is reliable, deletion is just a matter of dropping all references to the object. When a VALUE OBJECT is designated immutable in the design, developers are free to make decisions about issues such as copying and sharing on a purely technical basis, secure in the knowledge that the application does not rely on particular instances of the objects.
定义VALUE OBJECTS并将其指定为不可变是遵循一般规则的一种情况:避免模型中不必要的约束,让开发人员可以自由地进行纯技术性能调整。明确定义必要的约束,让开发人员可以调整设计,同时避免改变有意义的行为。此类设计调整通常非常特定于特定项目中使用的技术。
Defining VALUE OBJECTS and designating them as immutable is a case of following a general rule: Avoiding unnecessary constraints in a model leaves developers free to do purely technical performance tuning. Explicitly defining the essential constraints lets developers tweak the design while keeping safe from changing meaningful behavior. Such design tweaks are often very specific to the technology in use on a particular project.
从最低层级来看,数据库必须将数据放置在磁盘上的物理位置,而物理部件移动并读取数据需要时间。复杂的数据库会尝试将这些物理地址聚类,以便通过单个物理操作从磁盘中提取相关数据。
Databases, at the lowest level, have to place data in a physical location on a disk, and it takes time for physical parts to move around and read that data. Sophisticated databases attempt to cluster these physical addresses so that related data can be fetched from the disk in a single physical operation.
如果一个对象被许多其他对象引用,那么其中一些对象将不会位于附近(在同一页面上),需要额外的物理操作才能获取数据。通过制作副本,而不是共享对同一实例的引用,充当许多实体属性的VALUE OBJECT可以与使用它的每个实体存储在同一页面上。这种存储相同数据的多个副本的技术称为非规范化,通常在访问时间比存储空间或维护简单性更重要时使用。
If an object is referenced by many other objects, some of those objects will not be located nearby (on the same page), requiring an additional physical operation to get the data. By making a copy, rather than sharing a reference to the same instance, a VALUE OBJECT that is acting as an attribute of many ENTITIES can be stored on the same page as each ENTITY that uses it. This technique of storing multiple copies of the same data is called denormalization and is often used when access time is more critical than storage space or simplicity of maintenance.
在关系数据库中,您可能希望将特定VALUE放入拥有它的ENTITY的表中,而不是创建与单独表的关联。在分布式系统中,在另一台服务器上保存对VALUE OBJECT的引用可能会导致消息响应缓慢;相反,应该将整个对象的副本传递给另一台服务器。我们可以自由地制作这些副本,因为我们正在处理VALUE OBJECTS。
In a relational database, you might want to put a particular VALUE in the table of the ENTITY that owns it, rather than creating an association to a separate table. In a distributed system, holding a reference to a VALUE OBJECT on another server will probably make for slow responses to messages; instead, a copy of the whole object should be passed to the other server. We can freely make these copies because we are dealing with VALUE OBJECTS.
前面关于关联的讨论大部分适用于实体和值对象。模型中的关联越少越简单越好。
Most of the earlier discussion of associations applies to ENTITIES and VALUE OBJECTS alike. The fewer and simpler the associations in the model, the better.
但是,虽然ENTITIES之间的双向关联可能难以维持,但两个VALUE OBJECTS之间的双向关联毫无意义。如果没有身份,说一个对象指向指向它的同一个VALUE OBJECT是没有意义的。你最多可以说它指向一个与指向它的对象相等的对象,但你必须在某个地方强制执行这个不变量。虽然你可以这样做,并设置双向指针,但很难想到这样的安排的例子会很有用。尝试完全消除VALUE OBJECTS之间的双向关联。如果最终这种关联在您的模型中似乎是必要的,请重新考虑最初将对象声明为VALUE OBJECT 的决定。也许它有一个尚未明确识别的身份。
But, while bidirectional associations between ENTITIES may be hard to maintain, bidirectional associations between two VALUE OBJECTS just make no sense. Without identity, it is meaningless to say that an object points back to the same VALUE OBJECT that points to it. The most you could say is that it points to an object that is equal to the one pointing to it, but you would have to enforce that invariant somewhere. And although you could do so, and set up pointers going both ways, it is hard to think of examples where such an arrangement would be useful. Try to completely eliminate bidirectional associations between VALUE OBJECTS. If in the end such associations seem necessary in your model, rethink the decision to declare the object a VALUE OBJECT in the first place. Maybe it has an identity that hasn’t been explicitly recognized yet.
实体(E NTITIES)和值对象 (VALUE OBJECTS)是传统对象模型的主要元素,但务实的设计师已经开始使用另一个元素,服务 (SERVICES )......
ENTITIES and VALUE OBJECTS are the main elements of conventional object models, but pragmatic designers have come to use one other element, SERVICES. . . .
Sometimes, it just isn’t a thing.
在某些情况下,最清晰、最实用的设计包括概念上不属于任何对象的操作。我们可以遵循问题空间的自然轮廓,在模型中明确包含服务,而不是强行解决问题。
In some cases, the clearest and most pragmatic design includes operations that do not conceptually belong to any object. Rather than force the issue, we can follow the natural contours of the problem space and include SERVICES explicitly in the model.
有一些重要的领域操作无法在ENTITY或VALUE OBJECT中找到合适的位置。其中一些本质上是活动或动作,而不是事物,但由于我们的建模范例是对象,因此我们无论如何都会尝试将它们放入对象中。
There are important domain operations that can’t find a natural home in an ENTITY or VALUE OBJECT. Some of these are intrinsically activities or actions, not things, but since our modeling paradigm is objects, we try to fit them into objects anyway.
现在,更常见的错误是过于轻易地放弃将行为融入适当的对象,逐渐滑向过程式编程。但是,当我们将操作强行放入不符合对象定义的对象中时,该对象就会失去概念上的清晰度,变得难以理解或重构。复杂的操作很容易淹没一个简单的对象,模糊其角色。而且由于这些操作通常会将许多领域对象聚集在一起,协调它们并将它们付诸行动,因此增加的责任将对所有这些对象产生依赖,使原本可以独立理解的概念变得混乱。
Now, the more common mistake is to give up too easily on fitting the behavior into an appropriate object, gradually slipping toward procedural programming. But when we force an operation into an object that doesn’t fit the object’s definition, the object loses its conceptual clarity and becomes hard to understand or refactor. Complex operations can easily swamp a simple object, obscuring its role. And because these operations often draw together many domain objects, coordinating them and putting them into action, the added responsibility will create dependencies on all those objects, tangling concepts that could be understood independently.
有时服务会伪装成模型对象,表现为除了执行某些操作之外没有任何意义的对象。这些“执行者”最终会以“经理”等结尾的名称命名。他们它们没有自己的状态,在它们承载的操作之外的域中也没有任何意义。不过,至少这个解决方案为这些不同的行为提供了一个归宿,而不会弄乱真正的模型对象。
Sometimes services masquerade as model objects, appearing as objects with no meaning beyond doing some operation. These “doers” end up with names ending in “Manager” and the like. They have no state of their own nor any meaning in the domain beyond the operation they host. Still, at least this solution gives these distinct behaviors a home without messing up a real model object.
领域中的某些概念并不自然地被建模为对象。强制将所需的领域功能作为ENTITY或VALUE的责任要么会扭曲基于模型的对象的定义,要么会添加毫无意义的人造对象。
Some concepts from the domain aren’t natural to model as objects. Forcing the required domain functionality to be the responsibility of an ENTITY or VALUE either distorts the definition of a model-based object or adds meaningless artificial objects.
服务是作为模型中独立存在的接口提供的操作,不像实体和值对象那样封装状态。服务是技术框架中的常见模式,但它们也可以应用于领域层。
A SERVICE is an operation offered as an interface that stands alone in the model, without encapsulating state, as ENTITIES and VALUE OBJECTS do. SERVICES are a common pattern in technical frameworks, but they can also apply in the domain layer.
名称服务强调了与其他对象的关系。与实体和值对象不同,它纯粹是根据它能为客户端做什么来定义的。服务往往以活动而不是实体来命名——是动词而不是名词。服务仍然可以有一个抽象的、有意的定义;它只是与对象的定义不同而已。服务仍然应该有明确的职责,而且这个职责和履行这个职责的接口应该定义为领域模型的一部分。操作名称应该来自UBIQUITOUS LANGUAGE或被引入其中。参数和结果应该是领域对象。
The name service emphasizes the relationship with other objects. Unlike ENTITIES and VALUE OBJECTS, it is defined purely in terms of what it can do for a client. A SERVICE tends to be named for an activity, rather than an entity—a verb rather than a noun. A SERVICE can still have an abstract, intentional definition; it just has a different flavor than the definition of an object. A SERVICE should still have a defined responsibility, and that responsibility and the interface fulfilling it should be defined as part of the domain model. Operation names should come from the UBIQUITOUS LANGUAGE or be introduced into it. Parameters and results should be domain objects.
应谨慎使用服务,不允许剥夺实体和值对象的所有行为。但是,当操作实际上是一个重要的领域概念时,服务就自然而然地成为模型驱动设计的组成部分。在模型中声明为服务,而不是实际上不代表任何东西的虚假对象,独立操作不会误导任何人。
SERVICES should be used judiciously and not allowed to strip the ENTITIES and VALUE OBJECTS of all their behavior. But when an operation is actually an important domain concept, a SERVICE forms a natural part of a MODEL-DRIVEN DESIGN. Declared in the model as a SERVICE, rather than as a phony object that doesn’t actually represent anything, the standalone operation will not mislead anyone.
好的服务有三个特点。
A good SERVICE has three characteristics.
1.该操作涉及的领域概念不是ENTITY或VALUE OBJECT的自然组成部分。
1. The operation relates to a domain concept that is not a natural part of an ENTITY or VALUE OBJECT.
2.根据领域模型的其他元素来定义接口。
2. The interface is defined in terms of other elements of the domain model.
3.操作无状态。
3. The operation is stateless.
这里的无状态意味着任何客户端都可以使用特定服务的任何实例,而无需考虑该实例的单独历史记录。服务的执行将使用可访问的信息全局的,甚至可能改变全局信息(也就是说,它可能有副作用)。但是服务并不像大多数域对象那样拥有影响其自身行为的状态。
Statelessness here means that any client can use any instance of a particular SERVICE without regard to the instance’s individual history. The execution of a SERVICE will use information that is accessible globally, and may even change that global information (that is, it may have side effects). But the SERVICE does not hold state of its own that affects its own behavior, as most domain objects do.
当领域中的重要流程或转换不是ENTITY或VALUE OBJECT的自然职责时,请将操作作为独立接口添加到模型中,并将其声明为SERVICE。根据模型的语言定义接口,并确保操作名称是UBIQUITOUS LANGUAGE的一部分。使SERVICE无状态。
When a significant process or transformation in the domain is not a natural responsibility of an ENTITY or VALUE OBJECT, add an operation to the model as a standalone interface declared as a SERVICE. Define the interface in terms of the language of the model and make sure the operation name is part of the UBIQUITOUS LANGUAGE. Make the SERVICE stateless.
此模式专注于那些在领域中本身具有重要意义的服务,但服务当然不仅仅用于领域层。它小心区分属于领域层的服务与其他层的服务,并考虑职责以保持这种区别。
This pattern is focused on those SERVICES that have an important meaning in the domain in their own right, but of course SERVICES are not used only in the domain layer. It takes care to distinguish SERVICES that belong to the domain layer from those of other layers, and to factor responsibilities to keep that distinction sharp.
文献中讨论的大多数服务都是纯技术性的,属于基础结构层。领域和应用程序服务与这些基础结构服务协作。例如,银行可能有一个应用程序,当账户余额低于特定阈值时向客户发送电子邮件。封装电子邮件系统(可能还有其他通知方式)的接口是基础结构层中的服务。
Most SERVICES discussed in the literature are purely technical and belong in the infrastructure layer. Domain and application SERVICES collaborate with these infrastructure SERVICES. For example, a bank might have an application that sends an e-mail to a customer when an account balance falls below a specific threshold. The interface that encapsulates the e-mail system, and perhaps alternate means of notification, is a SERVICE in the infrastructure layer.
区分应用服务与领域服务可能比较困难。应用层负责对通知进行排序。领域层负责确定是否达到阈值 - 尽管此任务可能不需要服务,因为它符合“帐户”对象的职责。该银行应用程序可以负责资金转账。如果设计一项服务来为资金转账进行适当的借记和贷记,那么该功能将属于领域层。资金转账在银行领域语言中有意义,它涉及基本的业务逻辑。技术服务应该根本不需要任何业务含义。
It can be harder to distinguish application SERVICES from domain SERVICES. The application layer is responsible for ordering the notification. The domain layer is responsible for determining if a threshold was met—though this task probably does not call for a SERVICE, because it would fit the responsibility of an “account” object. That banking application could be responsible for funds transfers. If a SERVICE were devised to make appropriate debits and credits for a funds transfer, that capability would belong in the domain layer. Funds transfer has a meaning in the banking domain language, and it involves fundamental business logic. Technical SERVICES should lack any business meaning at all.
许多领域或应用服务都建立在实体和值的集合之上,其行为类似于脚本,可以组织领域中的潜力,以实际完成某件事。实体和值对象 通常粒度太细,无法方便地访问领域层的功能。在这里,我们遇到了领域层和应用层之间的一条非常细的界线。例如,如果银行应用程序可以将我们的交易转换并导出到电子表格文件中以供我们分析,则该导出就是应用程序服务。在银行领域中没有“文件格式”的含义,也不涉及任何业务规则。
Many domain or application SERVICES are built on top of the populations of ENTITIES and VALUES, behaving like scripts that organize the potential of the domain to actually get something done. ENTITIES and VALUE OBJECTS are often too fine-grained to provide a convenient access to the capabilities of the domain layer. Here we encounter a very fine line between the domain layer and the application layer. For example, if the banking application can convert and export our transactions into a spreadsheet file for us to analyze, that export is an application SERVICE. There is no meaning of “file formats” in the domain of banking, and there are no business rules involved.
另一方面,可以将资金从一个帐户转移到另一个帐户的功能是域服务,因为它嵌入了重要的业务规则(例如,贷记和借记相应的帐户),并且“资金转移”是一个有意义的银行术语。在这种情况下,服务本身不会做很多事情;它会要求两个帐户对象完成大部分工作。但将“转移”操作放在帐户对象上会很尴尬,因为该操作涉及两个帐户和一些全局规则。
On the other hand, a feature that can transfer funds from one account to another is a domain SERVICE because it embeds significant business rules (crediting and debiting the appropriate accounts, for example) and because a “funds transfer” is a meaningful banking term. In this case, the SERVICE does not do much on its own; it would ask the two Account objects to do most of the work. But to put the “transfer” operation on the Account object would be awkward, because the operation involves two accounts and some global rules.
我们可能希望创建一个资金转账对象来表示这两个条目以及转账的规则和历史记录。但我们仍然需要调用银行间网络中的服务。此外,在大多数开发系统中,在域对象和外部资源之间建立直接接口是很困难的。我们可以用FACADE来修饰这种外部服务,该 FACADE 接受模型方面的输入,也许会返回一个资金转账对象作为其结果。但无论我们可能拥有什么中介机构,即使它们不属于我们,这些服务也在履行资金转账的领域责任。
We might like to create a Funds Transfer object to represent the two entries plus the rules and history around the transfer. But we are still left with calls to SERVICES in the interbank networks. What’s more, in most development systems, it is awkward to make a direct interface between a domain object and external resources. We can dress up such external SERVICES with a FACADE that takes inputs in terms of the model, perhaps returning a Funds Transfer object as its result. But whatever intermediaries we might have, and even though they don’t belong to us, those SERVICES are carrying out the domain responsibility of funds transfer.
将服务划分为层
Partitioning Services into Layers
虽然这种模式讨论强调了将概念建模为服务的表现力,但该模式作为控制领域层接口粒度以及将客户端与实体和值对象分离的一种手段也很有价值。
Although this pattern discussion has emphasized the expressiveness of modeling a concept as a SERVICE, the pattern is also valuable as a means of controlling granularity in the interfaces of the domain layer, as well as decoupling clients from the ENTITIES and VALUE OBJECTS.
中等粒度、无状态的服务在大型系统中更容易重用,因为它们将重要的功能封装在一个简单的接口后面。此外,细粒度的对象可能会导致分布式系统中的消息传递效率低下。
Medium-grained, stateless SERVICES can be easier to reuse in large systems because they encapsulate significant functionality behind a simple interface. Also, fine-grained objects can lead to inefficient messaging in a distributed system.
如前所述,细粒度的域对象可能导致知识从域泄漏到应用层,而域对象的行为在应用层中进行协调。高度详细的交互的复杂性最终在应用层中处理,从而使域知识渗入应用程序或用户界面代码,而域层中的知识则丢失了。明智地引入域服务有助于保持各层之间的明确界限。
As previously discussed, fine-grained domain objects can contribute to knowledge leaks from the domain into the application layer, where the domain object’s behavior is coordinated. The complexity of a highly detailed interaction ends up being handled in the application layer, allowing domain knowledge to creep into the application or user interface code, where it is lost from the domain layer. The judicious introduction of domain services can help maintain the bright line between layers.
这种模式更注重界面的简单性,而不是客户端控制和多功能性。它提供了中等粒度的功能,在封装大型或分布式系统的组件时非常有用。有时,服务是表达领域概念的最自然的方式。
This pattern favors interface simplicity over client control and versatility. It provides a medium grain of functionality very useful in packaging components of large or distributed systems. And sometimes a SERVICE is the most natural way to express a domain concept.
分布式系统架构(例如 J2EE 和 CORBA)为服务提供了特殊的发布机制,并制定了使用约定,还增加了分发和访问功能。但是,此类框架在项目中并不总是被使用,即使被使用,如果动机只是关注点的逻辑分离,它们也可能被过度使用。
Distributed system architectures, such as J2EE and CORBA, provide special publishing mechanisms for SERVICES, with conventions for their use, and they add distribution and access capabilities. But such frameworks are not always in use on a project, and even when they are, they are likely to be overkill when the motivation is just a logical separation of concerns.
提供对SERVICE的访问的方式并不像划分特定职责的设计决策那么重要。“执行者”对象可能作为SERVICE接口的实现就足够了。可以轻松编写简单的SINGLETON(Gamma 等,1995)来提供访问。编码约定可以清楚地表明,这些对象只是SERVICE接口的交付机制,而不是有意义的域对象。只有当确实需要分发系统或以其他方式利用框架的功能时,才应使用复杂的架构。
The means of providing access to a SERVICE is not as important as the design decision to carve off specific responsibilities. A “doer” object may be satisfactory as an implementation of a SERVICE’s interface. A simple SINGLETON (Gamma et al. 1995) can be written easily to provide access. Coding conventions can make it clear that these objects are just delivery mechanisms for SERVICE interfaces, and not meaningful domain objects. Elaborate architectures should be used only when there is a real need to distribute the system or otherwise draw on the framework’s capabilities.
模块是一种古老而成熟的设计元素。虽然有技术方面的考虑,但认知超负荷是模块化的主要动机。模块为人们提供了两种模型视图:他们可以查看模块内的细节而不会被整体所淹没,或者他们可以在排除内部细节的视图中查看模块之间的关系。
MODULES are an old, established design element. There are technical considerations, but cognitive overload is the primary motivation for modularity. MODULES give people two views of the model: They can look at detail within a MODULE without being overwhelmed by the whole, or they can look at relationships between MODULES in views that exclude interior detail.
领域层中的模块应该作为模型的有意义的部分出现,在更大的范围内讲述领域的故事。
The MODULES in the domain layer should emerge as a meaningful part of the model, telling the story of the domain on a larger scale.
每个人都使用MODULES,但很少有人将其视为模型的完整部分。代码被分解成各种类别,从技术架构的各个方面到开发人员的工作任务。即使是经常进行重构的开发人员也倾向于满足于项目早期构思的MODULES 。
Everyone uses MODULES, but few treat them as a full-fledged part of the model. Code gets broken down into all sorts of categories, from aspects of the technical architecture to developers’ work assignments. Even developers who refactor a lot tend to content themselves with MODULES conceived early in the project.
模块之间应具有低耦合度,模块内部应具有高内聚度,这是不言而喻的。对耦合度和内聚度的解释往往使它们听起来像技术指标,可以根据关联和交互的分布进行机械判断。然而,划分模块的不仅仅是代码,还有概念。一个人同时思考的事情数量是有限的(因此耦合度低)。不连贯的思想片段与未分化的思想混合物一样难以理解(因此内聚度高)。
It is a truism that there should be low coupling between MODULES and high cohesion within them. Explanations of coupling and cohesion tend to make them sound like technical metrics, to be judged mechanically based on the distributions of associations and interactions. Yet it isn’t just code being divided into MODULES, but concepts. There is a limit to how many things a person can think about at once (hence low coupling). Incoherent fragments of ideas are as hard to understand as an undifferentiated soup of ideas (hence high cohesion).
低耦合和高内聚是适用于单个对象和模块的一般设计原则,但它们在更大的建模和设计粒度中尤为重要。这些术语已经存在很长时间了;可以在Larman 1998中找到一种模式风格的解释。
Low coupling and high cohesion are general design principles that apply as much to individual objects as to MODULES, but they are particularly important at this larger grain of modeling and design. These terms have been around for a long time; one patterns-style explanation can be found in Larman 1998.
每当两个模型元素被分成不同的模块时,它们之间的关系就会变得不像以前那么直接,这增加了了解它们在设计中的位置的开销。模块之间的低耦合可以最大限度地降低这种成本,并使得分析一个模块的内容时,只需参考最少的相互作用的其他模块即可。
Whenever two model elements are separated into different modules, the relationships between them become less direct than they were, which increases the overhead of understanding their place in the design. Low coupling between MODULES minimizes this cost, and makes it possible to analyze the contents of one MODULE with a minimum of reference to others that interact.
同时,一个好的模型的元素具有协同作用,精心选择的模块将具有特别丰富的概念关系的模型元素聚集在一起。具有相关职责的对象之间的这种高内聚性使建模和设计工作可以集中在一个模块中,这是人类思维可以轻松处理的复杂程度。
At the same time, the elements of a good model have synergy, and well-chosen MODULES bring together elements of the model with particularly rich conceptual relationships. This high cohesion of objects with related responsibilities allows modeling and design work to concentrate within a single MODULE, a scale of complexity a human mind can easily handle.
模块和较小的元素应该共同进化,但通常它们不会。选择模块是为了组织对象的早期形式。此后,对象往往会以使其保持在现有模块定义范围内的方式发生变化。重构模块比重构类更费力、更具破坏性,而且可能不会那么频繁。但正如模型对象往往从简单和具体开始,然后逐渐转变以揭示更深刻的见解一样,模块也可以变得微妙和抽象。让模块反映对领域不断变化的理解也将为其中的对象提供更多的自由度。
MODULES and the smaller elements should coevolve, but typically they do not. MODULES are chosen to organize an early form of the objects. After that, the objects tend to change in ways that keep them in the bounds of the existing MODULE definition. Refactoring MODULES is more work and more disruptive than refactoring classes, and probably can’t be as frequent. But just as model objects tend to start out naive and concrete and then gradually transform to reveal deeper insight, MODULES can become subtle and abstract. Letting the MODULES reflect changing understanding of the domain will also allow more freedom for the objects within them to evolve.
和领域驱动设计中的其他一切一样,模块是一种通信机制。被分割对象的含义需要推动模块的选择。当你将一些类放在一个模块中时,你是在告诉下一个查看你的设计的开发人员将它们放在一起思考。如果你的模型是在讲述一个故事,那么模块就是章节。模块的名称传达了它的含义。这些名称进入了通用语言。“现在让我们谈谈‘客户’模块”,你可能会对业务专家说,然后你们的谈话就背景化了。
Like everything else in a domain-driven design, MODULES are a communications mechanism. The meaning of the objects being partitioned needs to drive the choice of MODULES. When you place some classes together in a MODULE, you are telling the next developer who looks at your design to think about them together. If your model is telling a story, the MODULES are chapters. The name of the MODULE conveys its meaning. These names enter the UBIQUITOUS LANGUAGE. “Now let’s talk about the ‘customer’ module,” you might say to a business expert, and the context is set for your conversation.
所以:
Therefore:
选择能够讲述系统故事并包含一组内聚概念的模块。这通常会降低模块之间的耦合度,但如果不能,请寻找一种方法来更改模型以解开概念,或寻找一个被忽视的概念,该概念可能是模块的基础,可以将元素以有意义的方式组合在一起。寻求低耦合,即可以独立理解和推理的概念。优化模型,直到它根据高级领域概念进行划分,并且相应的代码也被解耦。
Choose MODULES that tell the story of the system and contain a cohesive set of concepts. This often yields low coupling between MODULES, but if it doesn’t, look for a way to change the model to disentangle the concepts, or search for an overlooked concept that might be the basis of a MODULE that would bring the elements together in a meaningful way. Seek low coupling in the sense of concepts that can be understood and reasoned about independently of each other. Refine the model until it partitions according to high-level domain concepts and the corresponding code is decoupled as well.
为模块命名,使其成为UBIQUITOUS LANGUAGE的一部分。模块及其名称应反映对领域的洞察力。
Give the MODULES names that become part of the UBIQUITOUS LANGUAGE. MODULES and their names should reflect insight into the domain.
考察概念关系并不是技术措施的替代方法。它们是同一问题的不同层次,两者都必须实现。但以模型为中心的思维会产生更深层次的解决方案,而不是偶然的解决方案。当必须权衡时,最好采用概念清晰度,即使这意味着MODULE之间有更多的引用,或者在对MODULE进行更改时偶尔会产生连锁反应。如果开发人员了解模型告诉他们的故事,他们就可以处理这些问题。
Looking at conceptual relationships is not an alternative to technical measures. They are different levels of the same issue, and both have to be accomplished. But model-focused thinking produces a deeper solution, rather than an incidental one. And when there has to be a trade-off, it is best to go with the conceptual clarity, even if it means more references between MODULES or occasional ripple effects when changes are made to a MODULE. Developers can handle these problems if they understand the story the model is telling them.
模块需要与模型的其余部分共同发展。这意味着模块需要与模型和代码一起重构。但这种重构通常不会发生。更改模块往往需要对代码进行广泛的更新。这样的更改可能会破坏团队沟通,甚至会破坏开发工具,例如源代码控制系统。因此,模块结构和名称通常比类反映出模型的早期形式。
MODULES need to coevolve with the rest of the model. This means refactoring MODULES right along with the model and code. But this refactoring often doesn’t happen. Changing MODULES tends to require widespread updates to the code. Such changes can be disruptive to team communication and can even throw a monkey wrench into development tools, such as source code control systems. As a result, MODULE structures and names often reflect much earlier forms of the model than the classes do.
模块选择中不可避免的早期错误会导致高耦合,从而难以重构。缺乏重构只会不断增加惯性。只有咬紧牙关,根据问题点所在的经验重组模块,才能克服这一问题。
Inevitable early mistakes in MODULE choices lead to high coupling, which makes it hard to refactor. The lack of refactoring just keeps increasing the inertia. It can only be overcome by biting the bullet and reorganizing MODULES based on experience of where the trouble spots lie.
一些开发工具和编程系统使问题更加严重。无论实施基于何种开发技术,我们都需要寻找方法来尽量减少重构MODULES的工作量,并尽量减少与其他开发人员沟通时的混乱。
Some development tools and programming systems exacerbate the problem. Whatever development technology the implementation will be based on, we need to look for ways of minimizing the work of refactoring MODULES, and minimizing clutter in communicating to other developers.
在 Java 中,导入(依赖项)必须在某个单独的类中声明。建模者可能认为包依赖于其他包,但在 Java 中却不能这样写。常见的编码约定鼓励导入特定的类,结果代码如下:
In Java, imports (dependencies) must be declared in some individual class. A modeler probably thinks of packages as depending on other packages, but this can’t be stated in Java. Common coding conventions encourage the import of specific classes, resulting in code like this:
ClassA1
导入包B.ClassB1;
导入包B.ClassB2;
导入包B.ClassB3;
导入包C.ClassC1;
导入包C.ClassC2;
导入包C.ClassC3;
...
ClassA1
import packageB.ClassB1;
import packageB.ClassB2;
import packageB.ClassB3;
import packageC.ClassC1;
import packageC.ClassC2;
import packageC.ClassC3;
. . .
不幸的是,在 Java 中,无法避免导入到单独的类中,但是至少可以一次导入整个包,这反映了包是高度内聚的单元的意图,同时减少了更改包名称的工作量。
In Java, unfortunately, there is no escape from importing into individual classes, but you can at least import entire packages at a time, reflecting the intention that packages are highly cohesive units while simultaneously reducing the effort of changing package names.
类A1
导入包B.*;
导入包C.*;
. . .
ClassA1
import packageB.*;
import packageC.*;
. . .
确实,这种技术意味着混合两种规模(类依赖于包),但它传达的信息比以前冗长的类列表更多——它传达了创建对特定模块的依赖关系的意图。
True, this technique means mixing two scales (classes depend on packages), but it communicates more than the previous voluminous list of classes—it conveys the intent to create a dependency on particular MODULES.
如果单个类确实依赖于另一个包中的特定类,并且本地MODULE似乎对其他MODULE没有概念上的依赖性,那么可能应该移动一个类,或者应该重新考虑MODULE本身。
If an individual class really does depend on a specific class in another package, and the local MODULE doesn’t seem to have a conceptual dependency on the other MODULE, then maybe a class should be moved, or the MODULES themselves should be reconsidered.
技术框架对我们的包装决策施加了强大的压力。其中一些是有帮助的,而另一些则需要抵制。
Strong forces on our packaging decisions come from technical frameworks. Some of these are helpful, while others need to be resisted.
一个非常有用的框架标准的例子是通过将基础设施和用户界面代码放入单独的包组中来实施分层架构,而将领域层物理地分离到它自己的一组包中。
An example of a very useful framework standard is the enforcement of LAYERED ARCHITECTURE by placing infrastructure and user interface code into separate groups of packages, leaving the domain layer physically separated into its own set of packages.
另一方面,分层架构可能会分散模型对象的实现。一些框架通过将单个域对象的职责分散到多个对象中,然后将这些对象放在单独的包中来创建层。例如,对于 J2EE,一种常见的做法是将数据和数据访问放入“实体 bean”,而将相关的业务逻辑放入“会话 bean”。除了增加每个组件的实现复杂性之外,这种分离还会立即剥夺对象模型的内聚性。对象的最基本概念之一是将数据与对该数据进行操作的逻辑封装在一起。这种分层实现并不致命,因为可以将两个组件视为共同构成单个模型元素的实现,但更糟糕的是,实体和会话 bean 通常会被分成不同的包。此时,查看各种对象并在心理上将它们重新组合成单个概念实体实在是太费劲了。我们失去了模型和设计之间的联系。最佳实践是使用比ENTITY对象粒度更大的 EJB ,以减少分层带来的不利影响。但细粒度对象通常也会被分成几层。
On the other hand, tiered architectures can fragment the implementation of the model objects. Some frameworks create tiers by spreading the responsibilities of a single domain object across multiple objects and then placing those objects in separate packages. For example, with J2EE a common practice is to place data and data access into an “entity bean” while placing associated business logic into a “session bean.” In addition to the increased implementation complexity of each component, the separation immediately robs an object model of cohesion. One of the most fundamental concepts of objects is to encapsulate data with the logic that operates on that data. This kind of tiered implementation is not fatal, because both components can be viewed as together constituting the implementation of a single model element, but to make matters worse, the entity and session beans are often separated into different packages. At that point, viewing the various objects and mentally fitting them back together as a single conceptual ENTITY is just too much effort. We lose the connection between the model and design. Best practice is to use EJBs at a larger grain than ENTITY objects, reducing the downside of separating tiers. But fine-grain objects are often split into tiers also.
例如,我在一个运行相当智能的项目中遇到了这些问题,该项目中每个概念对象实际上被分为四个层。每个部分都有很好的理由。第一层是数据持久层,处理映射和对关系数据库的访问。接下来是处理对象在所有情况下固有行为的层。接下来是用于叠加特定于应用程序的功能的层。第四层是作为公共接口,与下面的所有实现分离。这个方案有点太复杂了,但层次定义得很好,关注点分离也比较整洁。我们可以在精神上将所有物理对象连接到一个概念对象上。有时,方面的分离甚至有帮助。特别是,将持久性代码移出可以消除很多混乱。
For example, I encountered these problems on a rather intelligently run project in which each conceptual object was actually broken into four tiers. Each division had a good rationale. The first tier was a data persistence layer, handling mapping and access to the relational database. Then came a layer that handled behavior intrinsic to the object in all situations. Next was a layer for superimposing application-specific functionality. The fourth tier was meant as a public interface, decoupled from all the implementation below. This scheme was a bit too complicated, but the layers were well defined and there was some tidiness to the separation of concerns. We could have lived with mentally connecting all the physical objects making up one conceptual object. The separation of aspects even helped at times. In particular, having the persistence code moved out removed a lot of clutter.
但除此之外,该框架还要求每一层都位于一组单独的包中,并根据标识该层的惯例进行命名。这占用了进行分区的所有脑力空间。因此,领域开发人员倾向于避免制作太多的模块(每个模块都乘以 4),并且几乎从不更改模块,因为重构模块的工作量太大了。更糟糕的是,追踪定义单个概念类的所有数据和行为非常困难(再加上分层的间接性),以至于开发人员没有太多的心理空间来考虑模型。应用程序已交付,但其领域模型贫乏,该模型基本满足了应用程序的数据库访问要求,行为由少数服务提供。模型驱动设计本应带来的杠杆作用有限,因为代码没有透明地展示模型并允许开发人员使用它。
But on top of all this, the framework required each tier to be in a separate set of packages, named according to a convention that identified the tier. This took up all the mental room for partitioning. As a result, domain developers tended to avoid making too many MODULES (each of which was multiplied by four) and hardly ever changed one, because the effort of refactoring a MODULE was prohibitive. Worse, hunting down all the data and behavior that defined a single conceptual class was so difficult (combined with the indirectness of the layering) that developers didn’t have much mental space left to think about models. The application was delivered, but with an anemic domain model that basically fulfilled the database access requirements of the application, with behavior supplied by a few SERVICES. The leverage that should have derived from MODEL-DRIVEN DESIGN was limited because the code did not transparently reveal the model and allow a developer to work with it.
这种框架设计试图解决两个合法问题。一个是关注点的逻辑划分:一个对象负责数据库访问,另一个对象负责业务逻辑,等等。这样的划分使得更容易理解每一层的功能(在技术层面上),也使得切换层更容易。问题是,应用程序开发的成本没有被认识到。这不是一本关于框架设计的书,所以我不会讨论这个问题的替代解决方案,但它们确实存在。即使没有选择,最好还是用这些好处来换取一个更有凝聚力的领域层。
This kind of framework design is attempting to address two legitimate issues. One is the logical division of concerns: One object has responsibility for database access, another for business logic, and so on. Such divisions make it easier to understand the functioning of each tier (on a technical level) and make it easier to switch out layers. The trouble is that the cost to application development is not recognized. This is not a book on framework design, so I won’t go into alternative solutions to that problem, but they do exist. And even if there were no options, it would be better to trade off these benefits for a more cohesive domain layer.
这些打包方案的另一个动机是分层分布。如果代码实际上部署在不同的服务器上,这可能是一个强有力的论据。通常情况并非如此。灵活性只是为了在需要时才寻求的。对于希望从模型驱动设计中获益的项目来说,除非它能解决一个迫在眉睫的问题,否则这种牺牲太大了。
The other motivation for these packaging schemes is the distribution of tiers. This could be a strong argument if the code actually got deployed on different servers. Usually it does not. The flexibility is sought just in case it is needed. On a project that hopes to get leverage from MODEL-DRIVEN DESIGN, this sacrifice is too great unless it solves an immediate and pressing problem.
精心设计的技术驱动包装方案会产生两项成本。
Elaborate technically driven packaging schemes impose two costs.
• 如果框架的分区约定将实现概念对象的元素分开,则代码不再显示模型。
• If the framework’s partitioning conventions pull apart the elements implementing the conceptual objects, the code no longer reveals the model.
• 一个人能够将有限的分区重新拼接起来,如果框架将其全部用完,领域开发人员就失去了将模型分成有意义的部分的能力。
• There is only so much partitioning a mind can stitch back together, and if the framework uses it all up, the domain developers lose their ability to chunk the model into meaningful pieces.
最好保持简单。选择对技术环境必不可少或实际上有助于开发的最低限度的技术分区规则。例如,将复杂的数据分离从对象的行为方面理解持久性代码可能会使重构变得更容易。
It is best to keep things simple. Choose a minimum of technical partitioning rules that are essential to the technical environment or actually aid development. For example, decoupling complicated data persistence code from the behavioral aspects of the objects may make refactoring easier.
除非真正打算将代码分发到不同的服务器上,否则请将实现单个概念对象的所有代码放在同一个MODULE中,如果不是同一个对象的话。
Unless there is a real intention to distribute code on different servers, keep all the code that implements a single conceptual object in the same MODULE, if not the same object.
我们可以通过借鉴旧标准“高内聚/低耦合”得出相同的结论。实现业务逻辑的“对象”和负责数据库访问的“对象”之间的连接非常广泛,以至于耦合度非常高。
We could have come to the same conclusion by drawing on the old standard, “high cohesion/low coupling.” The connections between an “object” implementing the business logic and the one responsible for database access are so extensive that the coupling is very high.
还存在其他陷阱,即框架设计或公司或项目的惯例可能会通过模糊域对象的自然内聚性来破坏模型驱动设计,但底线是一样的。这些限制或仅仅是大量必需的包,排除了使用针对域模型需求量身定制的其他打包方案的可能性。
There are other pitfalls where framework design or just conventions of a company or project can undermine MODEL-DRIVEN DESIGN by obscuring the natural cohesion of the domain objects, but the bottom line is the same. The restrictions, or just the large number of required packages, rules out the use of other packaging schemes that are tailored to the needs of the domain model.
使用打包将域层与其他代码分开。否则,请尽可能地让域开发人员自由地以支持其模型和设计选择的方式打包域对象。
Use packaging to separate the domain layer from other code. Otherwise, leave as much freedom as possible to the domain developers to package the domain objects in ways that support their model and design choices.
当代码是基于声明式设计生成的(第 10 章讨论)时,会出现一种例外情况。在这种情况下,开发人员不需要阅读代码,最好将其放入单独的包中,这样就不会妨碍开发人员实际使用的设计元素。
One exception arises when code is generated based on a declarative design (discussed in Chapter 10). In that case, the developers do not need to read the code, and it is better to put it into a separate package so that it is out of the way, not cluttering up the design elements developers actually have to work with.
随着设计规模越来越大、越来越复杂,模块化变得越来越重要。本节介绍了基本注意事项。第IV 部分“战略设计”的大部分内容提供了打包和分解大型模型和设计的方法,以及如何为人们提供焦点以指导理解。
Modularity becomes more critical as the design gets bigger and more complex. This section presents the basic considerations. Much of Part IV, “Strategic Design,” provides approaches to packaging and breaking down big models and designs, and ways to give people focal points to guide understanding.
领域模型中的每个概念都应反映在实现元素中。ENTITIES、VALUE OBJECTS及其关联,以及一些领域SERVICES和组织MODULES是实现和模型之间的直接对应点。 实现中的对象、指针和检索机制必须映射到模型元素显然,这是很直接的。如果他们不这样做,那么就清理代码,返回并更改模型,或者两者兼而有之。
Each concept from the domain model should be reflected in an element of implementation. The ENTITIES, VALUE OBJECTS, and their associations, along with a few domain SERVICES and the organizing MODULES, are points of direct correspondence between the implementation and the model. The objects, pointers, and retrieval mechanisms in the implementation must map to model elements straightforwardly, obviously. If they do not, clean up the code, go back and change the model, or both.
抵制向领域对象添加与其所代表的概念不密切相关的任何内容的诱惑。这些设计元素有其工作要做:它们表达模型。为了使系统正常工作,必须执行其他与领域相关的职责并管理其他数据,但它们不属于这些对象。在第6 章中,我将讨论一些履行领域层技术职责的支持对象,例如定义数据库搜索和封装复杂的对象创建。
Resist the temptation to add anything to the domain objects that does not closely relate to the concepts they represent. These design elements have their job to do: they express the model. There are other domain-related responsibilities that must be carried out and other data that must be managed in order to make the system work, but they don’t belong in these objects. In Chapter 6, I will discuss some supporting objects that fulfill the technical responsibilities of the domain layer, such as defining database searches and encapsulating complex object creation.
本章中的四种模式提供了对象模型的构建块。但模型驱动设计并不一定意味着将所有内容都强行放入对象模型中。还有其他由工具支持的模型范例,例如规则引擎。项目必须在它们之间做出务实的权衡。这些其他工具和技术是实现模型驱动设计的手段,而不是替代方法。
The four patterns in this chapter provide the building blocks for an object model. But MODEL-DRIVEN DESIGN does not necessarily mean forcing everything into an object mold. There are also other model paradigms supported by tools, such as rules engines. Projects have to make pragmatic trade-offs between them. These other tools and techniques are means to the end of a MODEL-DRIVEN DESIGN, not alternatives to it.
模型驱动设计要求实现技术与所应用的特定建模范式相一致。许多此类范式都已尝试过,但只有少数在实践中得到广泛应用。目前,占主导地位的范式是面向对象设计,如今大多数复杂项目都开始使用对象。这种主导地位的形成有多种原因:一些因素是对象固有的,一些因素是偶然的,而另一些因素则源于广泛使用本身带来的优势。
MODEL-DRIVEN DESIGN calls for an implementation technology in tune with the particular modeling paradigm being applied. Many such paradigms have been experimented with, but only a few have been widely used in practice. At present, the dominant paradigm is object-oriented design, and most complex projects these days set out to use objects. This predominance has come about for a variety of reasons: some factors are intrinsic to objects, some are circumstantial, and others derive from the advantages that come from wide usage itself.
团队选择对象范式的很多原因都不是技术性的,甚至不是对象本身的。但从一开始,对象建模就确实在简单性和复杂性之间取得了良好的平衡。
Many of the reasons teams choose the object paradigm are not technical, or even intrinsic to objects. But right out of the gate, object modeling does strike a nice balance of simplicity and sophistication.
如果建模范式太深奥,那么掌握它的开发人员就不够多,而且他们也会用得不好。如果团队中非技术成员连范式的基础知识都掌握不了,他们就无法理解模型,通用语言就会迷失。面向对象设计的基本原理似乎对大多数人来说都是自然而然的。尽管有些开发人员错过了建模的微妙之处,但即使是非技术人员也可以理解对象模型的图表。
If a modeling paradigm is too esoteric, not enough developers will master it, and they will use it badly. If the nontechnical members of the team can’t grasp at least the rudiments of the paradigm, they will not understand the model, and the UBIQUITOUS LANGUAGE will be lost. The fundamentals of object-oriented design seem to come naturally to most people. Although some developers miss the subtleties of modeling, even nontechnologists can follow a diagram of an object model.
然而,尽管对象建模的概念很简单,但它已被证明足够丰富,可以捕捉重要的领域知识。而且它从一开始就得到了开发工具的支持,允许在软件中表达模型。
Yet, simple as the concept of object modeling is, it has proven rich enough to capture important domain knowledge. And it has been supported from the outset by development tools that allowed a model to be expressed in software.
如今,对象范式还具有一些显著的环境优势,这些优势源于其成熟度和广泛采用。如果没有成熟的基础设施和工具支持,项目可能会转向技术研发,从而延迟和转移应用程序开发的资源,并带来技术风险。有些技术与其他技术不能很好地协同工作,可能无法将它们与行业标准解决方案集成,从而迫使团队重新发明通用实用程序。但多年来,这些问题中的许多问题已经为对象解决了,或者由于广泛采用而变得无关紧要。(现在,与主流对象技术的集成取决于其他方法。)大多数新技术都提供了与流行的面向对象平台集成的方法。这使得集成更加容易,甚至留下了基于其他建模范式的子系统混合的选项(我们将在本章后面讨论)。
Today, the object paradigm also has some significant circumstantial advantages deriving from maturity and widespread adoption. Without mature infrastructure and tool support, a project can get sidetracked into technological R&D, delaying and diverting resources away from application development and introducing technical risks. Some technologies don’t play well with others, and it may not be possible to integrate them with industry-standard solutions, forcing the team to reinvent common utilities. But over the years, many of these problems have been solved for objects, or made irrelevant by widespread adoption. (Now it falls on other approaches to integrate with mainstream object technology.) Most new technologies provide the means to integrate with the popular object-oriented platforms. This makes integration easier and even leaves open the option of mixing in subsystems based on other modeling paradigms (which we will discuss later in this chapter).
同样重要的是开发者社区和设计文化本身的成熟度。采用新范式的项目可能无法找到精通该技术的开发人员,或无法找到在所选范式中创建有效模型的经验丰富的开发人员。在合理的时间内培训开发人员可能不可行,因为充分利用范式和技术的模式尚未形成。也许该领域的先驱者是有效的,但尚未以易于理解的形式发表他们的见解。
Equally important is the maturity of the developer community and the design culture itself. A project that adopts a novel paradigm may be unable to find developers with expertise in the technology, or with the experience to create effective models in the chosen paradigm. It may not be feasible to educate developers in a reasonable amount of time because the patterns for making the most of the paradigm and technology haven’t gelled yet. Perhaps the pioneers of the field are effective but haven’t yet published their insights in an accessible form.
由数千名开发人员、项目经理以及参与项目工作的所有其他专家组成的社区已经了解对象。
Objects are already understood by a community of thousands of developers, project managers, and all the other specialists involved in project work.
十年前的一个面向对象项目的故事说明了在不成熟的范式下工作的风险。20 世纪 90 年代初,该项目致力于多项尖端技术,包括大规模使用面向对象数据库。这令人兴奋。团队成员会自豪地告诉访客,我们正在部署这项技术有史以来支持的最大的数据库。当我加入该项目时,不同的团队正在推出面向对象的设计,并毫不费力地将他们的对象存储在数据库中。但我们逐渐意识到,我们开始占用数据库容量的很大一部分 - 以及测试数据!实际数据库将大几十倍。实际交易量将高出几十倍。这项技术是否不可能用于这个应用程序?我们是否使用不当?我们力不从心。
A story from an object-oriented project of only a decade ago illustrates the risks of working in an immature paradigm. In the early 1990s, this project committed itself to several cutting-edge technologies, including use of an object-oriented database on a large scale. It was exciting. People on the team would proudly tell visitors that we were deploying the biggest database this technology had ever supported. When I joined the project, different teams were spinning out object-oriented designs and storing their objects in the database effortlessly. But gradually the realization crept upon us that we were beginning to absorb a significant fraction of the database’s capacity—with test data! The actual database would be dozens of times larger. The actual transaction volume would be dozens of times higher. Was it impossible to use this technology for this application? Had we used it improperly? We were out of our depth.
幸运的是,我们能够从世界上为数不多的几个有能力帮助我们解决这个问题的人中,找到其中一位加入我们的团队。他开出了价格,我们付了钱。问题有三个根源。首先,数据库提供的现成基础设施根本无法满足我们的需求。其次,存储细粒度对象的成本比我们想象的要高得多。第三,对象模型的各部分相互依赖关系错综复杂,以至于并发事务数量相对较少时,争用就会成为问题。
Fortunately, we were able to bring onto the team one of a handful of people in the world with the skills to extricate us from the problem. He named his price and we paid it. There were three sources of the problem. First, the off-the-shelf infrastructure provided with the database simply didn’t scale up to our needs. Second, storage of fine-grained objects turned out to be much more costly than we had realized. Third, parts of the object model had such a tangle of interdependencies that contention became a problem with a relatively small number of concurrent transactions.
在这位聘请的专家的帮助下,我们增强了基础设施。团队现在意识到了细粒度对象的影响,开始寻找更适合这项技术的模型。我们所有人都加深了对限制模型中关系网的重要性的思考,并开始运用这种新的理解来制作更好的模型,使紧密相关的聚合体之间更加解耦。
With the help of this hired expert, we enhanced the infrastructure. The team, now aware of the impact of fine-grained objects, began to find models that worked better with this technology. All of us deepened our thinking about the importance of limiting the web of relationships in a model, and we began applying this new understanding to making better models with more decoupling between closely interrelated aggregates.
这次恢复工作浪费了好几个月的时间,再加上之前几个月的失败。这并不是团队第一次因为所选技术不成熟以及我们自己缺乏相关学习曲线的经验而遭遇挫折。遗憾的是,这个项目最终缩减了规模,变得相当保守。直到今天,他们仍在使用这些奇特的技术,但这些技术只用于谨慎界定的应用,可能并没有真正从中受益。
Several months were lost in this recovery, in addition to the earlier months spent going down a failed path. And this had not been the team’s first setback resulting from the immaturity of the chosen technologies and our own lack of experience with the associated learning curve. Sadly, this project eventually retrenched and became quite conservative. To this day they use the exotic technologies, but for cautiously scoped applications that probably don’t really benefit from them.
十年后,面向对象技术已经相对成熟。大多数常见的基础设施需求都可以通过现场使用的现成解决方案来满足。关键任务工具来自主要供应商(通常是多个供应商)或稳定的开源项目。许多这些基础设施本身已经得到广泛使用,以至于已经有一批人了解它们,也有书籍解释它们,等等。这些成熟技术的局限性相当容易理解,因此知识渊博的团队不太可能过度使用。
A decade later, object-oriented technology is relatively mature. Most common infrastructure needs can be met with off-the-shelf solutions that have been used in the field. Mission-critical tools come from major vendors, often multiple vendors, or from stable open-source projects. Many of these infrastructure pieces themselves are used widely enough that there is a base of people who already understand them, as well as books explaining them, and so forth. The limitations of these established technologies are fairly well understood, so that knowledgeable teams are less likely to overreach.
其他有趣的建模范式则没有这种成熟度。有些太难掌握,除了小专业外永远不会使用。其他的有潜力,但技术基础设施仍然不完善或不稳定,很少有人理解为它们创建良好模型的微妙之处。这些可能已经成熟,但它们还没有为大多数项目做好准备。
Other interesting modeling paradigms just don’t have this maturity. Some are too hard to master and will never be used outside small specialties. Others have potential, but the technical infrastructure is still patchy or shaky, and few people understand the subtleties of creating good models for them. These may come of age, but they are not ready for most projects.
这就是为什么目前大多数尝试模型驱动设计的项目都明智地使用面向对象技术作为其系统的核心。它们不会被锁定在纯对象系统中——因为对象已成为行业的主流,集成工具可用于连接目前使用的几乎任何其他技术。
This is why, for the present, most projects attempting MODEL-DRIVEN DESIGN are wise to use object-oriented technology as the core of their system. They will not be locked into an object-only system—because objects have become the mainstream of the industry, integration tools are available to connect with almost any other technology in current use.
但这并不意味着人们应该永远局限于对象。随大流确实能提供一些安全,但并不总是正确的选择。对象模型解决了大量实际的软件问题,但有些领域并不适合用离散的封装行为包来建模。例如,数学性很强的领域或以全局逻辑推理为主的领域就不太适合面向对象范式。
Yet this doesn’t mean that people should restrict themselves to objects forever. Traveling with the crowd provides some safety, but it isn’t always the way to go. Object models address a large number of practical software problems, but there are domains that are not natural to model as discrete packets of encapsulated behavior. For example, domains that are intensely mathematical or that are dominated by global logical reasoning do not fit well into the object-oriented paradigm.
领域模型不一定是对象模型。例如,在 Prolog 中实现了模型驱动设计,其模型由逻辑规则和事实组成。模型范式被设想用于解决人们喜欢思考领域的某些方式。然后,这些领域的模型由范式塑造。结果是一个符合范式的模型,以便它可以在支持该建模风格的工具中有效地实现。
A domain model does not have to be an object model. There are MODEL-DRIVEN DESIGNS implemented in Prolog, for example, with a model made up of logical rules and facts. Model paradigms have been conceived to address certain ways people like to think about domains. Then the models of those domains are shaped by the paradigm. The result is a model that conforms to the paradigm so that it can be effectively implemented in the tools that support that modeling style.
无论项目中的主导模型范式是什么,领域中必然存在一些部分在其他范式中更容易表达。当领域中只有少数异常元素在某个范式中运行良好时,开发人员可以忍受在其他方面一致的模型中出现的一些尴尬对象。(或者,在另一个极端,如果问题领域的大部分在某个特定的其他范式中更自然地表达,那么完全切换范式并选择不同的实现平台可能是有意义的。)但是,当领域的主要部分似乎属于不同的范式时,使用多种工具集来支持实现,在适合的范式中对每个部分进行建模在智力上很有吸引力。当相互依赖性较小时,可以封装其他范式中的子系统,例如只需由对象调用的复杂数学计算。其他时候,不同方面更加交织在一起,例如当对象的交互依赖于某些数学关系时。
Whatever the dominant model paradigm may be on a project, there are bound to be parts of the domain that would be much easier to express in some other paradigm. When there are just a few anomalous elements of a domain that otherwise works well in a paradigm, developers can live with a few awkward objects in an otherwise consistent model. (Or, on the other extreme, if the greater part of the problem domain is more naturally expressed in a particular other paradigm, it may make sense to switch paradigms altogether and choose a different implementation platform.) But when major parts of the domain seem to belong to different paradigms, it is intellectually appealing to model each part in a paradigm that fits, using a mixture of tool sets to support implementation. When the interdependence is small, a subsystem in the other paradigm can be encapsulated, such as a complex math calculation that simply needs to be called by an object. Other times the different aspects are more intertwined, such as when the interaction of the objects depends on some mathematical relationships.
这就是将业务规则引擎和工作流引擎等非对象组件集成到对象系统中的动机。混合范式允许开发人员以最适合的风格对特定概念进行建模。此外,大多数系统必须使用一些非对象技术基础设施,最常见的是关系数据库。但创建一个跨范式的连贯模型很难,而让支持工具共存也很复杂。当开发人员无法清楚地看到软件中体现的连贯模型时,模型驱动设计可能会被抛到九霄云外,即使这种混合增加了对它的需求。
This is what motivates the integration into object systems of such nonobject components as business rules engines and workflow engines. Mixing paradigms allows developers to model particular concepts in the style that fits best. Furthermore, most systems must use some nonobject technical infrastructure, most commonly relational databases. But making a coherent model that spans paradigms is hard, and making the supporting tools coexist is complicated. When developers can’t clearly see a coherent model embodied in the software, MODEL-DRIVEN DESIGN can go out the window, even as this mixture increases the need for it.
规则引擎将作为有时混入面向对象应用程序开发项目的技术的示例。知识丰富的领域模型可能包含显式规则,但对象范式缺乏用于陈述规则及其交互的特定语义。尽管规则可以建模为对象,并且通常可以成功建模,但对象封装使得应用跨整个系统的全局规则变得很困难。规则引擎技术之所以有吸引力,是因为它承诺提供一种更自然、更具声明性的方式来定义规则,从而有效地将规则范式混合到对象范式中。逻辑范式已经发展完善,功能强大,它似乎是对对象优缺点的一个很好的补充。
Rules engines will serve as an example of a technology sometimes mixed into an object-oriented application development project. A knowledge-rich domain model probably contains explicit rules, yet the object paradigm lacks specific semantics for stating rules and their interactions. Although rules can be modeled as objects, and often are successfully, object encapsulation makes it awkward to apply global rules that cross the whole system. Rules engine technology is appealing because it promises to provide a more natural and declarative way to define rules, effectively allowing the rules paradigm to be mixed into the object paradigm. The logic paradigm is well developed and powerful, and it seems like a good complement to the strengths and weaknesses of objects.
但人们并不总是能从规则引擎中得到他们所期望的东西。有些产品就是不能很好地工作。有些产品缺乏无缝视图,无法显示两个实施环境之间运行的模型概念的相关性。一个常见的结果是应用程序一分为二:一个使用对象的静态数据存储系统,以及一个几乎与对象模型失去所有联系的临时规则处理应用程序。
But people don’t always get what they hope for out of rules engines. Some products just don’t work very well. Some lack a seamless view that can show the relatedness of model concepts that run between the two implementation environments. One common outcome is an application fractured in two: a static data storage system using objects, and an ad hoc rules processing application that has lost almost all connection with the object model.
在使用规则时,继续从模型的角度思考很重要。团队必须找到一个可以与两种实现范例兼容的单一模型。这并不容易,但如果规则引擎允许表达性实现,这应该是可能的。否则,数据和规则就会变得脱节。引擎中的规则最终更像是小程序,而不是领域模型中的概念规则。通过规则和对象之间紧密、清晰的关系,可以保留这两个部分的含义。
It is important to continue to think in terms of models while working with rules. The team has to find a single model that can work with both implementation paradigms. This is not easy, but it should be possible if the rules engine allows expressive implementation. Otherwise, the data and the rules become unconnected. The rules in the engine end up more like little programs than conceptual rules in the domain model. With tight, clear relationships between the rules and the objects, the meaning of both pieces is retained.
如果没有无缝的环境,开发人员就需要提炼出一个由清晰的基本概念组成的模型来将整个设计整合在一起。
Without a seamless environment, it falls on the developers to distill a model made up of clear, fundamental concepts to hold the whole design together.
将各部分结合在一起的最有效工具是整个异构模型所依赖的强大的UBIQUITOUS LANGUAGE 。在两个环境中一致地应用名称并在UBIQUITOUS LANGUAGE中使用这些名称可以帮助弥合差距。
The most effective tool for holding the parts together is a robust UBIQUITOUS LANGUAGE that underlies the whole heterogeneous model. Consistently applying names in the two environments and exercising those names in the UBIQUITOUS LANGUAGE can help bridge the gap.
这个话题值得写一本书来专门讨论。本节的目的仅仅是为了说明没有必要放弃模型驱动设计,而且值得为保留它付出努力。
This is a topic that deserves a book of its own. The goal of this section is merely to show that it isn’t necessary to give up MODEL-DRIVEN DESIGN, and that it is worth the effort to keep it.
尽管模型驱动设计不必面向对象,但它确实依赖于模型构造的表达性实现,无论是对象、规则还是工作流。如果可用的工具不支持这种表达性,请重新考虑工具的选择。缺乏表达力的实现会抵消额外范式的优势。
Although a MODEL-DRIVEN DESIGN does not have to be object oriented, it does depend on having an expressive implementation of the model constructs, be they objects, rules, or workflows. If the available tool does not facilitate that expressiveness, reconsider the choice of tools. An unexpressive implementation negates the advantage of the extra paradigm.
以下是将非对象元素混合到以面向对象为主的系统中四条经验法则:
Here are four rules of thumb for mixing nonobject elements into a predominantly object-oriented system:
•不要与实施范式作斗争。总是有另一种方式来思考领域。找到适合范式的模型概念。
• Don’t fight the implementation paradigm. There’s always another way to think about a domain. Find model concepts that fit the paradigm.
•依靠通用语言。即使工具之间没有严格的联系,使用非常一致的语言也可以防止设计各部分出现分歧。
• Lean on the ubiquitous language. Even when there is no rigorous connection between tools, very consistent use of language can keep parts of the design from diverging.
•不要拘泥于 UML。有时,对某个工具(如 UML 图表)的执着会导致人们扭曲模型,使其符合可以轻松绘制的内容。例如,UML 确实具有一些表示约束的功能,但它们并不总是足够的。其他绘图风格(可能是其他范式的常规风格)或简单的英语描述比曲折地改编用于特定对象视图的绘图风格要好。
• Don’t get hung up on UML. Sometimes the fixation on a tool, such as UML diagramming, leads people to distort the model to make it fit what can easily be drawn. For example, UML does have some features for representing constraints, but they are not always sufficient. Some other style of drawing (perhaps conventional for the other paradigm), or simple English descriptions, are better than tortuous adaptation of a drawing style intended for a certain view of objects.
•保持怀疑态度。该工具真的发挥了作用吗?仅仅因为您有一些规则,并不一定意味着您需要规则引擎的开销。规则可以表示为对象,也许不太简洁;多种范式使事情变得非常复杂。
• Be skeptical. Is the tool really pulling its weight? Just because you have some rules, that doesn’t necessarily mean you need the overhead of a rules engine. Rules can be expressed as objects, perhaps a little less neatly; multiple paradigms complicate matters enormously.
在承担混合范式的负担之前,应该先用尽主导范式中的选项。即使某些领域概念没有以明显的对象形式出现,它们通常也可以在范式中建模。第 9 章将讨论使用对象技术对非常规类型的概念进行建模。
Before taking on the burden of mixed paradigms, the options within the dominant paradigm should be exhausted. Even though some domain concepts don’t present themselves as obvious objects, they often can be modeled within the paradigm. Chapter 9 will discuss the modeling of unconventional types of concepts using object technology.
关系范式是范式混合的一个特例。关系数据库是最常见的非对象技术,它与对象模型的关系也比其他组件更密切,因为它充当构成对象本身的数据的持久存储。第6 章将讨论将对象数据存储在关系数据库中,以及对象生命周期的许多其他挑战。
The relational paradigm is a special case of paradigm mixing. The most common nonobject technology, the relational database is also more intimately related to the object model than other components, because it acts as the persistent store of the data that makes up the objects themselves. Storing object data in relational databases will be discussed in Chapter 6, along with the many other challenges of the object life cycle.
每个对象都有生命周期。对象诞生后,可能会经历各种状态,最终消亡——被存档或删除。当然,其中许多都是简单的、瞬时的对象,只需调用其构造函数即可创建,用于某些计算,然后被垃圾收集器丢弃。没有必要使这些对象复杂化。但其他对象的寿命更长,并非全部都在活动内存中度过。它们与其他对象具有复杂的相互依赖关系。它们会经历不变量适用的状态变化。管理这些对象带来了挑战,这些挑战很容易破坏模型驱动设计的尝试。
Every object has a life cycle. An object is born, it likely goes through various states, and it eventually dies—being either archived or deleted. Of course, many of these are simple, transient objects, created with an easy call to their constructor, used in some computation, and then abandoned to the garbage collector. There is no need to complicate such objects. But other objects have longer lives, not all of which are spent in active memory. They have complex interdependencies with other objects. They go through changes of state to which invariants apply. Managing these objects presents challenges that can easily derail an attempt at MODEL-DRIVEN DESIGN.
图 6.1。领域对象的生命周期
Figure 6.1. The life cycle of a domain object
The challenges fall into two categories.
1.在整个生命周期中保持完整性
1. Maintaining integrity throughout the life cycle
2.防止模型因管理生命周期的复杂性而陷入困境
2. Preventing the model from getting swamped by the complexity of managing the life cycle
本章将通过三种模式来解决这些问题。首先,聚合通过定义明确的所有权和边界来收紧模型本身,避免对象网络混乱、错综复杂。这种模式对于在生命周期的所有阶段保持完整性至关重要。
This chapter will address these issues through three patterns. First, AGGREGATES tighten up the model itself by defining clear ownership and boundaries, avoiding a chaotic, tangled web of objects. This pattern is crucial to maintaining integrity in all phases of the life cycle.
接下来,重点转向生命周期的开始,使用FACTORIES创建和重构复杂对象和AGGREGATES,保持其内部结构封装。最后,REPOSITORIES解决生命周期的中间和结束问题,提供查找和检索持久对象的方法,同时封装所涉及的庞大基础设施。
Next, the focus turns to the beginning of the life cycle, using FACTORIES to create and reconstitute complex objects and AGGREGATES, keeping their internal structure encapsulated. Finally, REPOSITORIES address the middle and end of the life cycle, providing the means of finding and retrieving persistent objects while encapsulating the immense infrastructure involved.
尽管REPOSITORIES和FACTORIES本身并非来自领域,但它们在领域设计中扮演着重要的角色。这些构造通过为我们提供对模型对象的可访问句柄,完善了模型驱动设计。
Although REPOSITORIES and FACTORIES do not themselves come from the domain, they have meaningful roles in the domain design. These constructs complete the MODEL-DRIVEN DESIGN by giving us accessible handles on the model objects.
对AGGREGATES进行建模并在设计中添加FACTORIES和REPOSITORIES使我们能够在整个生命周期内系统地、以有意义的单位操纵模型对象。AGGREGATES标记了生命周期每个阶段必须保持不变的范围。F ACTORIES和REPOSITORIES对AGGREGATES进行操作,封装了特定生命周期转换的复杂性。
Modeling AGGREGATES and adding FACTORIES and REPOSITORIES to the design gives us the ability to manipulate the model objects systematically and in meaningful units throughout their life cycle. AGGREGATES mark off the scope within which invariants have to be maintained at every stage of the life cycle. FACTORIES and REPOSITORIES operate on AGGREGATES, encapsulating the complexity of specific life cycle transitions.
关联设计的极简主义有助于简化遍历并在一定程度上限制关系的激增,但大多数业务领域相互关联如此紧密,以至于我们最终仍需要通过对象引用来追踪长而深的路径。在某种程度上,这种混乱反映了现实世界的现实,现实世界很少要求我们设定明确的界限。这是软件设计中的一个问题。
Minimalist design of associations helps simplify traversal and limit the explosion of relationships somewhat, but most business domains are so interconnected that we still end up tracing long, deep paths through object references. In a way, this tangle reflects the realities of the world, which seldom obliges us with sharp boundaries. It is a problem in a software design.
假设你从数据库中删除了一个 Person 对象。删除对象时会删除其姓名、出生日期和工作描述。但是地址怎么办?同一地址可能还有其他人。如果删除该地址,这些 Person 对象将引用已删除的对象。如果保留该对象,则会在数据库中积累垃圾地址。自动垃圾收集可以消除垃圾地址,但即使数据库系统中有这种技术修复,也会忽略一个基本的建模问题。
Say you were deleting a Person object from a database. Along with the person go a name, birth date, and job description. But what about the address? There could be other people at the same address. If you delete the address, those Person objects will have references to a deleted object. If you leave it, you accumulate junk addresses in the database. Automatic garbage collection could eliminate the junk addresses, but that technical fix, even if available in your database system, ignores a basic modeling issue.
即使考虑一个独立的事务,典型对象模型中的关系网也没有明确限制变更的潜在影响。刷新系统中的每个对象(以防存在某种依赖关系)是不切实际的。
Even when considering an isolated transaction, the web of relationships in a typical object model gives no clear limit to the potential effect of a change. It is not practical to refresh every object in the system, just in case there is some dependency.
在多个客户端同时访问同一对象的系统中,这个问题尤为严重。由于许多用户查阅和更新系统中的不同对象,我们必须防止同时更改相互依赖的对象。范围错误会带来严重后果。
The problem is acute in a system with concurrent access to the same objects by multiple clients. With many users consulting and updating different objects in the system, we have to prevent simultaneous changes to interdependent objects. Getting the scope wrong has serious consequences.
很难保证具有复杂关联的模型中对象更改的一致性。需要维护适用于紧密相关的对象组(而不仅仅是离散对象)的不变量。然而,谨慎的锁定方案会导致多个用户毫无意义地相互干扰,并使系统无法使用。
It is difficult to guarantee the consistency of changes to objects in a model with complex associations. Invariants need to be maintained that apply to closely related groups of objects, not just discrete objects. Yet cautious locking schemes cause multiple users to interfere pointlessly with each other and make a system unusable.
换句话说,我们如何知道由其他对象组成的对象从哪里开始和结束?在任何具有持久数据存储的系统中,必须有一个更改数据的事务范围,以及一种维护数据一致性(即维护其不变量)的方法。数据库允许各种锁定方案,并且可以对测试进行编程。但这些临时解决方案会分散您对模型的注意力,很快您就会回到黑客攻击和希望之中。
Put another way, how do we know where an object made up of other objects begins and ends? In any system with persistent storage of data, there must be a scope for a transaction that changes data, and a way of maintaining the consistency of the data (that is, maintaining its invariants). Databases allow various locking schemes, and tests can be programmed. But these ad hoc solutions divert attention away from the model, and soon you are back to hacking and hoping.
事实上,要找到平衡的解决此类问题的方法,需要对领域有更深入的了解,这一次需要扩展到某些类的实例之间变化频率等因素。我们需要找到一个模型,让高竞争点更宽松,让严格不变量更严格。
In fact, finding a balanced solution to these kinds of problems calls for deeper understanding of the domain, this time extending to factors such as the frequency of change between the instances of certain classes. We need to find a model that leaves high-contention points looser and strict invariants tighter.
尽管这个问题表面上是数据库事务中的技术难题,但它的根源在于模型——缺乏明确的边界。由模型驱动的解决方案将使模型更易于理解,并使设计更易于沟通。随着模型的修订,它将指导我们对实施的更改。
Although this problem surfaces as technical difficulties in database transactions, it is rooted in the model—in its lack of defined boundaries. A solution driven from the model will make the model easier to understand and make the design easier to communicate. As the model is revised, it will guide our changes to the implementation.
已经开发了用于定义模型中的所有权关系的方案。以下简单但严格的系统是从这些概念中提炼出来的,包括一组用于实施修改对象及其所有者的交易的规则。1
Schemes have been developed for defining ownership relationships in the model. The following simple but rigorous system, distilled from those concepts, includes a set of rules for implementing transactions that modify the objects and their owners.1
首先,我们需要一个抽象来封装模型中的引用。AGGREGATE是一组关联对象,我们将其视为数据更改的单位。每个AGGREGATE都有一个根和一个边界。边界定义了AGGREGATE内部的内容。根是AGGREGATE中包含的单个特定实体。根是AGGREGATE中唯一允许外部对象引用的成员,尽管边界内的对象可以相互引用。除根之外的实体具有本地身份,但该身份只需要在AGGREGATE内区分,因为任何外部对象都无法在根实体的上下文之外看到它。
First we need an abstraction for encapsulating references within the model. An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each AGGREGATE has a root and a boundary. The boundary defines what is inside the AGGREGATE. The root is a single, specific ENTITY contained in the AGGREGATE. The root is the only member of the AGGREGATE that outside objects are allowed to hold references to, although objects within the boundary may hold references to each other. ENTITIES other than the root have local identity, but that identity needs to be distinguishable only within the AGGREGATE, because no outside object can ever see it out of the context of the root ENTITY.
汽车模型可能用于汽车修理店的软件中。汽车是具有全局身份的实体:我们希望将该汽车与世界上所有其他汽车区分开来,即使是非常相似的汽车。我们可以使用车辆识别号来实现这一点,这是分配给每辆新车的唯一标识符。我们可能希望通过四个车轮位置跟踪轮胎的旋转历史。我们可能想知道每个轮胎的行驶里程和胎面磨损情况。要知道哪个轮胎是哪个,轮胎也必须被识别为实体。但是,我们不太可能关心该特定汽车之外的轮胎身份。如果我们更换轮胎并将旧轮胎送到回收厂,要么我们的软件将不再跟踪它们,要么它们将成为一堆轮胎的匿名成员。没有人会关心它们的旋转历史。更重要的是,即使它们附在汽车上,也没有人会尝试查询系统以查找特定轮胎,然后查看它在哪辆车上。它们会查询数据库以查找汽车,然后要求其提供对轮胎的临时引用。因此,汽车是AGGREGATE的根实体,其边界也包含轮胎。另一方面,发动机缸体上刻有序列号,有时会独立于汽车进行跟踪。在某些应用程序中,发动机可能是其自身AGGREGATE的根。
A model of a car might be used in software for an auto repair shop. The car is an ENTITY with global identity: we want to distinguish that car from all other cars in the world, even very similar ones. We can use the vehicle identification number for this, a unique identifier assigned to each new car. We might want to track the rotation history of the tires through the four wheel positions. We might want to know the mileage and tread wear of each tire. To know which tire is which, the tires must be identified ENTITIES also. But it is very unlikely that we care about the identity of those tires outside of the context of that particular car. If we replace the tires and send the old ones to a recycling plant, either our software will no longer track them at all, or they will become anonymous members of a heap of tires. No one will care about their rotation histories. More to the point, even while they are attached to the car, no one will try to query the system to find a particular tire and then see which car it is on. They will query the database to find a car and then ask it for a transient reference to the tires. Therefore, the car is the root ENTITY of the AGGREGATE whose boundary encloses the tires also. On the other hand, engine blocks have serial numbers engraved on them and are sometimes tracked independently of the car. In some applications, the engine might be the root of its own AGGREGATE.
图 6.2. 本地与全局身份和对象引用
Figure 6.2. Local versus global identity and object references
不变量是数据发生变化时必须维护的一致性规则,它将涉及AGGREGATE成员之间的关系。任何跨越AGGREGATES的规则都不会始终保持最新状态。通过事件处理、批处理或其他更新机制,可以在指定时间内解决其他依赖关系。但是,在 AGGREGATE 中应用的不变量将在每个事务完成时强制执行。
Invariants, which are consistency rules that must be maintained whenever data changes, will involve relationships between members of the AGGREGATE. Any rule that spans AGGREGATES will not be expected to be up-to-date at all times. Through event processing, batch processing, or other update mechanisms, other dependencies can be resolved within some specified time. But the invariants applied within an AGGREGATE will be enforced with the completion of each transaction.
图 6.3. GGREGATE不变量
Figure 6.3. AGGREGATE invariants
现在,为了将概念上的AGGREGATE转化为实现,我们需要一组适用于所有交易的规则。
Now, to translate that conceptual AGGREGATE into the implementation, we need a set of rules to apply to all transactions.
• 根实体具有全局身份,并最终负责检查不变量。
• The root ENTITY has global identity and is ultimately responsible for checking invariants.
• 根实体具有全局标识。边界内的实体具有本地标识,仅在聚合内唯一。
• Root ENTITIES have global identity. ENTITIES inside the boundary have local identity, unique only within the AGGREGATE.
• AGGREGATE边界之外的任何内容都不能保存对内部任何内容的引用,但对根ENTITY除外。根ENTITY可以将对内部ENTITIES的引用传递给其他对象,但这些对象只能暂时使用它们,并且它们可能不会保留引用。根可能会将VALUE OBJECT的副本交给另一个对象,而它会发生什么并不重要,因为它只是一个VALUE,不再与AGGREGATE 有任何关联。
• Nothing outside the AGGREGATE boundary can hold a reference to anything inside, except to the root ENTITY. The root ENTITY can hand references to the internal ENTITIES to other objects, but those objects can use them only transiently, and they may not hold on to the reference. The root may hand a copy of a VALUE OBJECT to another object, and it doesn’t matter what happens to it, because it’s just a VALUE and no longer will have any association with the AGGREGATE.
• 作为上一条规则的推论,只有AGGREGATE根可以通过数据库查询直接获取。所有其他对象都必须通过关联遍历来找到。
• As a corollary to the previous rule, only AGGREGATE roots can be obtained directly with database queries. All other objects must be found by traversal of associations.
• AGGREGATE内的对象可以保存对其他AGGREGATE根的引用。
• Objects within the AGGREGATE can hold references to other AGGREGATE roots.
• 删除操作必须一次性删除AGGREGATE边界内的所有内容。(使用垃圾收集,这很容易。因为除了根之外没有对任何内容的外部引用,所以删除根,其他所有内容都将被收集。)
• A delete operation must remove everything within the AGGREGATE boundary at once. (With garbage collection, this is easy. Because there are no outside references to anything but the root, delete the root and everything else will be collected.)
• 当提交对AGGREGATE边界内的任何对象的更改时,必须满足整个AGGREGATE的所有不变量。
• When a change to any object within the AGGREGATE boundary is committed, all invariants of the whole AGGREGATE must be satisfied.
将实体和值对象聚类到聚合中,并定义每个聚合的边界。选择一个实体作为每个聚合的根,并通过根控制对边界内对象的所有访问。允许外部对象仅保存对根的引用。对内部成员的瞬时引用只能在单个操作中传递出去使用。由于根控制访问,因此它不会因内部更改而措手不及。这种安排使得在任何状态更改时强制执行聚合中对象和整个聚合的所有不变量变得切实可行。
Cluster the ENTITIES and VALUE OBJECTS into AGGREGATES and define boundaries around each. Choose one ENTITY to be the root of each AGGREGATE, and control all access to the objects inside the boundary through the root. Allow external objects to hold references to the root only. Transient references to internal members can be passed out for use within a single operation only. Because the root controls access, it cannot be blindsided by changes to the internals. This arrangement makes it practical to enforce all invariants for objects in the AGGREGATE and for the AGGREGATE as a whole in any state change.
拥有一个允许你声明AGGREGATES然后自动执行锁定方案等的技术框架会非常有帮助。如果没有这种帮助,团队必须有自律性,就 AGGREGATES 达成一致并按照它们编写代码。
It can be very helpful to have a technical framework that allows you to declare AGGREGATES and then automatically carries out the locking scheme and so forth. Without that assistance, the team must have the self-discipline to agree on the AGGREGATES and code consistently with them.
考虑简化的采购订单系统中可能出现的复杂情况。
Consider the complications possible in a simplified purchase order system.
图 6.4. 采购订单系统模型
Figure 6.4. A model for a purchase order system
此图显示了采购订单 (PO) 的非常传统的视图,该订单被分解为行项目,并有一个不变的规则,即行项目的总和不能超过整个采购订单的限制。现有的实施存在三个相互关联的问题。
This diagram presents a pretty conventional view of a purchase order (PO), broken down into line items, with an invariant rule that the sum of the line items can’t exceed the limit for the PO as a whole. The existing implementation has three interrelated problems.
1. 不变的执行。当添加新的行项目时,采购订单会检查总数,如果项目超过限额,采购订单会将其标记为无效。正如我们所看到的,这并不是足够的保护。
1. Invariant enforcement. When a new line item is added, the PO checks the total and marks itself invalid if an item pushes it over the limit. As we’ll see, this is not adequate protection.
2. 变更管理。当采购订单被删除或存档时,订单项会被带走,但模型并没有给出在何处停止跟踪关系的指导。在不同时间更改零件价格的影响也令人困惑。
2. Change management. When the PO is deleted or archived, the line items are taken along, but the model gives no guidance on where to stop following the relationships. There is also confusion about the impact of changing the part price at different times.
3. 共享数据库。多个用户在数据库中造成争用问题。
3. Sharing the database. Multiple users are creating contention problems in the database.
多个用户将同时输入和更新各种采购订单,我们必须防止他们弄乱彼此的工作。让我们从一个非常简单的策略开始,在该策略中,我们锁定用户开始编辑的任何对象,直到该用户提交事务。因此,当 George 编辑行项目 001 时,Amanda 无法访问它。她可以编辑任何其他采购订单上的任何其他行项目(包括 George 正在处理的采购订单中的其他项目)。
Multiple users will be entering and updating various POs concurrently, and we have to prevent them from messing up each other’s work. Let’s start with a very simple strategy, in which we lock any object a user begins to edit until that user commits the transaction. So, when George is editing line item 001, Amanda cannot access it. She can edit any other line item on any other PO (including other items in the PO George is working on).
图 6.5. 数据库中存储的 PO 的初始条件
Figure 6.5. The initial condition of the PO stored in the database
对象将从数据库中读取并在每个用户的内存空间中实例化。在那里,可以查看和编辑它们。只有在编辑开始时才会请求数据库锁定。因此,只要 George 和 Amanda 远离彼此的项目,他们就可以同时工作。一切都很顺利……直到 George 和 Amanda 开始处理同一采购订单中的不同项目。
Objects will be read from the database and instantiated in each user’s memory space. There they can be viewed and edited. Database locks will be requested only when an edit begins. So both George and Amanda can work concurrently, as long as they stay away from each other’s items. All is well . . . until both George and Amanda start working on separate line items in the same PO.
图 6.6. 不同事务中的同时编辑
Figure 6.6. Simultaneous edits in distinct transactions
对于用户和他们的软件来说一切都看起来很好,因为他们忽略了交易期间发生的数据库其他部分的更改,并且锁定的项目都不涉及其他用户的更改。
Everything looks fine to both users and to their software because they ignore changes to other parts of the database that happen during the transaction, and neither locked line item is involved in the other user’s change.
图 6.7. 最终的 PO 违反了批准限制(破坏了不变量)。
Figure 6.7. The resulting PO violates the approval limit (broken invariant).
在两个用户都保存了更改后,数据库中存储了一个违反领域模型不变量的 PO。一条重要的业务规则被打破了。但没有人知道。
After both users have saved their changes, a PO is stored in the database that violates the invariant of the domain model. An important business rule has been broken. And nobody even knows.
显然,锁定单个订单项并不是一个足够的保障。如果我们一次锁定整个采购订单,这个问题就可以避免。
Clearly, locking a single line item isn’t an adequate safeguard. If instead we had locked an entire PO at a time, the problem would have been prevented.
图 6.8。锁定整个 PO 可以强制执行不变量。
Figure 6.8. Locking the entire PO allows the invariant to be enforced.
程序将不允许保存此交易,直到 Amanda 解决了该问题(可能是通过提高限额或取消一把吉他)。此机制可防止出现此问题,如果工作主要分散在许多采购订单上,这可能是一个不错的解决方案。但如果多个人通常同时处理大型采购订单的不同行项目,那么这种锁定将变得繁琐。
The program will not allow this transaction to be saved until Amanda has resolved the problem, perhaps by raising the limit or by eliminating a guitar. This mechanism prevents the problem, and it may be a fine solution if work is mostly spread widely across many POs. But if multiple people typically work simultaneously on different line items of a large PO, then this locking will get cumbersome.
即使假设有许多小订单,也有其他方式违反断言。考虑一下那个“部分”。如果有人在 Amanda 添加订单时更改了长号的价格,这是否也会违反不变量?
Even assuming many small POs, there are other ways to violate the assertion. Consider that “part.” If someone changed the price of a trombone while Amanda was adding to her order, wouldn’t that violate the invariant too?
让我们尝试锁定整个采购订单之外的部分。以下是当 George、Amanda 和 Sam 处理不同的采购订单时发生的情况:
Let’s try locking the part in addition to the entire PO. Here’s what happens when George, Amanda, and Sam are working on different POs:
图 6.9。过于谨慎的锁定正在干扰人们的工作。
Figure 6.9. Over-cautious locking is interfering with people’s work.
不便之处正在加剧,因为对仪器(“零件”)的争夺非常激烈。然后:
The inconvenience is mounting, because there is a lot of contention for the instruments (the “parts”). And then:
图 6.10. 死锁
Figure 6.10. Deadlock
那三个人要等一会儿了。
Those three will be waiting a while.
At this point we can begin to improve the model by incorporating the following knowledge of the business:
1.零部件用于许多采购订单(竞争激烈)。
1. Parts are used in many POs (high contention).
2.零件的更改比采购订单的更改少。
2. There are fewer changes to parts than there are to POs.
3.零件价格的变更不一定会影响现有的采购订单。这取决于相对于采购订单状态的价格变更时间。
3. Changes to part prices do not necessarily propagate to existing POs. It depends on the time of a price change relative to the status of the PO.
当我们考虑已交付的存档采购订单时,第 3 点尤其明显。当然,它们应该显示填写时的价格,而不是当前价格。
Point 3 is particularly obvious when we consider archived POs that have already been delivered. They should, of course, show the prices as of the time they were filled, rather than current prices.
图 6.11. 价格被复制到Line Item中。现在可以强制执行GGREGATE不变量。
Figure 6.11. Price is copied into Line Item. AGGREGATE invariant can now be enforced.
与此模型一致的实现将保证 PO 及其项目的不变性,而零件价格的变化不必立即影响引用它的项目。更广泛的一致性规则可以通过其他方式解决。例如,系统可以每天向用户显示一个过期价格的项目队列,以便他们可以更新或免除每个项目。但这不是必须始终执行的不变量。通过使项目对零件的依赖性更松散,我们可以避免争用并更好地反映业务的现实情况。同时,加强 PO 及其项目之间的关系可确保遵循重要的业务规则。
An implementation consistent with this model would guarantee the invariant relating PO and its items, while changes to the price of a part would not have to immediately affect the items that reference it. Broader consistency rules could be addressed in other ways. For example, the system could present a queue of items with outdated prices to the users each day, so they could update or exempt each one. But this is not an invariant that must be enforced at all times. By making the dependency of line items on parts looser, we avoid contention and reflect the realities of the business better. At the same time, tightening the relationship of the PO and its line items guarantees that an important business rule will be followed.
AGGREGATE赋予 PO 及其项目的所有权,这与商业惯例一致。创建和删除采购订单 (PO) 和项目 (Item) 自然地联系在一起,而部件 (Part) 的创建和删除则是独立的。
The AGGREGATE imposes an ownership of the PO and its items that is consistent with business practice. The creation and deletion of a PO and items are naturally tied together, while the creation and deletion of parts is independent.
AGGREGATES标出了生命周期每个阶段必须维护的不变量的范围。以下模式FACTORIES和REPOSITORIES操作AGGREGATES,封装了特定生命周期转换的复杂性。...
AGGREGATES mark off the scope within which invariants have to be maintained at every stage of the life cycle. The following patterns, FACTORIES and REPOSITORIES, operate on AGGREGATES, encapsulating the complexity of specific life cycle transitions. . . .
当创建一个对象或整个AGGREGATE变得复杂或暴露太多内部结构时,FACTORIES可以提供封装。
When creation of an object, or an entire AGGREGATE, becomes complicated or reveals too much of the internal structure, FACTORIES provide encapsulation.
对象的大部分功能在于其内部结构和关联的复杂配置。对象应该被精简,直到没有任何东西与其含义无关或不支持其在交互中的作用。这种中期生命周期责任很多。问题源于让一个复杂的对象承担其自身创建的责任。
Much of the power of objects rests in the intricate configuration of their internals and their associations. An object should be distilled until nothing remains that does not relate to its meaning or support its role in interactions. This mid-life cycle responsibility is plenty. Problems arise from overloading a complex object with responsibility for its own creation.
汽车发动机是一种复杂的机械装置,有几十个零件相互协作,共同完成发动机的职责:转动轴。我们可以设想设计一个发动机缸体,它能抓住一组活塞,将它们插入气缸,火花塞能找到插座,自己拧进去。但这种复杂的机器似乎不太可能像我们典型的发动机那样可靠或高效。相反,我们接受其他东西来组装这些零件。也许是人类机械师,也许是它将成为工业机器人。机器人和人类实际上都比他们组装的发动机更复杂。组装零件的工作与旋转轴的工作完全无关。组装工只在汽车制造过程中工作——开车时不需要机器人或机械师陪同。因为汽车从来不会同时组装和驾驶,所以将这两项功能组合到同一个机制中是没有意义的。同样,组装一个复杂的复合物体的工作最好与该物体完成后要做的任何工作分开。
A car engine is an intricate piece of machinery, with dozens of parts collaborating to perform the engine’s responsibility: to turn a shaft. One could imagine trying to design an engine block that could grab on to a set of pistons and insert them into its cylinders, spark plugs that would find their sockets and screw themselves in. But it seems unlikely that such a complicated machine would be as reliable or as efficient as our typical engines are. Instead, we accept that something else will assemble the pieces. Perhaps it will be a human mechanic or perhaps it will be an industrial robot. Both the robot and the human are actually more complex than the engine they assemble. The job of assembling parts is completely unrelated to the job of spinning a shaft. The assemblers function only during the creation of the car—you don’t need a robot or a mechanic with you when you’re driving. Because cars are never assembled and driven at the same time, there is no value in combining both of these functions into the same mechanism. Likewise, assembling a complex compound object is a job that is best separated from whatever job that object will have to do when it is finished.
但是将责任转移给另一个相关方,即应用程序中的客户端对象,会导致更严重的问题。客户端知道需要完成什么工作,并依赖域对象执行必要的计算。如果希望客户端组装所需的域对象,它必须了解对象的内部结构。为了强制执行适用于域对象中各部分关系的所有不变量,客户端必须了解对象的某些规则。即使调用构造函数也会将客户端与其正在构建的对象的具体类耦合在一起。如果不更改客户端,就无法对域对象的实现进行任何更改,这使得重构更加困难。
But shifting responsibility to the other interested party, the client object in the application, leads to even worse problems. The client knows what job needs to be done and relies on the domain objects to carry out the necessary computations. If the client is expected to assemble the domain objects it needs, it must know something about the internal structure of the object. In order to enforce all the invariants that apply to the relationship of parts in the domain object, the client must know some of the object’s rules. Even calling constructors couples the client to the concrete classes of the objects it is building. No change to the implementation of the domain objects can be made without changing the client, making refactoring harder.
客户端负责创建对象会变得非常复杂,并模糊其职责。它破坏了域对象和正在创建的聚合的封装。更糟糕的是,如果客户端是应用层的一部分,那么职责就完全从域层中泄露出来了。应用程序与实现细节的紧密耦合剥夺了域层抽象的大部分好处,并使持续更改变得更加昂贵。
A client taking on object creation becomes unnecessarily complicated and blurs its responsibility. It breaches the encapsulation of the domain objects and the AGGREGATES being created. Even worse, if the client is part of the application layer, then responsibilities have leaked out of the domain layer altogether. This tight coupling of the application to the specifics of the implementation strips away most of the benefits of abstraction in the domain layer and makes continuing changes ever more expensive.
创建对象本身可能是一项重要操作,但复杂的组装操作并不适合创建对象的责任。将这些责任结合起来会产生难以理解的笨拙设计。让客户端直接构造会使客户端的设计变得混乱,破坏组装对象或AGGREGATE的封装,并使客户端与创建对象的实现过度耦合。
Creation of an object can be a major operation in itself, but complex assembly operations do not fit the responsibility of the created objects. Combining such responsibilities can produce ungainly designs that are hard to understand. Making the client direct construction muddies the design of the client, breaches encapsulation of the assembled object or AGGREGATE, and overly couples the client to the implementation of the created object.
复杂对象的创建是领域层的责任,但该任务不属于表达模型的对象。在某些情况下,对象的创建和组装对应于领域中重要的里程碑,例如“开设银行账户”。但对象创建和组装通常在领域中没有意义;它们是实现的必需品。为了解决这个问题,我们必须向领域设计中添加不是实体、值对象或服务的结构。这与上一章不同,重要的是要明确这一点:我们正在向设计中添加与模型中的任何内容都不对应的元素,但它们仍然是领域层职责的一部分。
Complex object creation is a responsibility of the domain layer, yet that task does not belong to the objects that express the model. There are some cases in which an object creation and assembly corresponds to a milestone significant in the domain, such as “open a bank account.” But object creation and assembly usually have no meaning in the domain; they are a necessity of the implementation. To solve this problem, we have to add constructs to the domain design that are not ENTITIES, VALUE OBJECTS, or SERVICES. This is a departure from the previous chapter, and it is important to make the point clear: We are adding elements to the design that do not correspond to anything in the model, but they are nonetheless part of the domain layer’s responsibility.
每种面向对象语言都提供了一种创建对象的机制(例如,Java 和 C++ 中的构造函数、Smalltalk 中的实例创建类方法),但需要更抽象的构造机制,使其与其他对象分离。负责创建其他对象的程序元素称为FACTORY。
Every object-oriented language provides a mechanism for creating objects (constructors in Java and C++, instance creation class methods in Smalltalk, for example), but there is a need for more abstract construction mechanisms that are decoupled from the other objects. A program element whose responsibility is the creation of other objects is called a FACTORY.
图 6.12. 与FACTORY 的基本交互
Figure 6.12. Basic interactions with a FACTORY
正如对象的接口应该封装其实现,从而允许客户端使用对象的行为而无需了解其工作原理一样,FACTORY封装了创建复杂对象或AGGREGATE所需的知识。它提供了一个反映客户端目标的接口和所创建对象的抽象视图。
Just as the interface of an object should encapsulate its implementation, thus allowing a client to use the object’s behavior without knowing how it works, a FACTORY encapsulates the knowledge needed to create a complex object or AGGREGATE. It provides an interface that reflects the goals of the client and an abstract view of the created object.
所以:
Therefore:
将创建复杂对象和聚合实例的责任转移到单独的对象,该对象本身可能在域模型中没有任何责任,但仍然是域设计的一部分。提供一个封装所有复杂组件的接口,并且不需要客户端引用正在实例化的对象的具体类。将整个聚合作为一个整体创建,并强制执行它们的不变量。
Shift the responsibility for creating instances of complex objects and AGGREGATES to a separate object, which may itself have no responsibility in the domain model but is still part of the domain design. Provide an interface that encapsulates all complex assembly and that does not require the client to reference the concrete classes of the objects being instantiated. Create entire AGGREGATES as a piece, enforcing their invariants.
设计工厂的方法有很多种。Gamma等人在 1995 年的著作中详细讨论了几种专用创建模式——工厂方法、抽象工厂和构建器。该书主要探讨了最困难的对象构造问题的模式。这里的重点不是深入研究工厂的设计,而是展示工厂作为领域设计的重要组成部分的地位。正确使用工厂可以帮助模型驱动设计保持正轨。
There are many ways to design FACTORIES. Several special-purpose creation patterns—FACTORY METHOD, ABSTRACT FACTORY, and BUILDER—were thoroughly treated in Gamma et al. 1995. That book mostly explored patterns for the most difficult object construction problems. The point here is not to delve deeply into designing FACTORIES, but rather to show the place of FACTORIES as important components of a domain design. Proper use of FACTORIES can help keep a MODEL-DRIVEN DESIGN on track.
任何优秀工厂的两个基本要求是
The two basic requirements for any good FACTORY are
1.每种创建方法都是原子的,并强制执行所创建对象或AGGREGATE的所有不变量。FACTORY应该只能生成处于一致状态的对象。对于 ENTITY 来说,这意味着创建整个AGGREGATE,满足所有不变量,但可能仍需添加可选元素。对于不可变的VALUE OBJECT来说,这意味着所有属性都已初始化为其正确的最终状态。如果接口允许请求无法正确创建的对象,则应引发异常或调用其他机制以确保不会出现不正确的返回值。
1. Each creation method is atomic and enforces all invariants of the created object or AGGREGATE. A FACTORY should only be able to produce an object in a consistent state. For an ENTITY, this means the creation of the entire AGGREGATE, with all invariants satisfied, but probably with optional elements still to be added. For an immutable VALUE OBJECT, this means that all attributes are initialized to their correct final state. If the interface makes it possible to request an object that can’t be created correctly, then an exception should be raised or some other mechanism should be invoked that will ensure that no improper return value is possible.
2. FACTORY应该抽象为所需的类型,而不是创建的具体类。Gamma等人在 1995 年提出的复杂FACTORY模式有助于实现这一点。
2. The FACTORY should be abstracted to the type desired, rather than the concrete class(es) created. The sophisticated FACTORY patterns in Gamma et al. 1995 help with this.
一般来说,你会创建一个工厂来构建你想要隐藏其细节的东西,并将 FACTORY 放置在你想要控制的位置。这些决定通常围绕AGGREGATES展开。
Generally speaking, you create a factory to build something whose details you want to hide, and you place the FACTORY where you want the control to be. These decisions usually revolve around AGGREGATES.
例如,如果您需要在已存在的AGGREGATE中添加元素,则可以在AGGREGATE的根上创建一个FACTORY METHOD 。这样可以向任何外部客户端隐藏AGGREGATE内部的实现,同时让根负责确保在添加元素时AGGREGATE的完整性,如下页图 6.13所示。
For example, if you needed to add elements inside a preexisting AGGREGATE, you might create a FACTORY METHOD on the root of the AGGREGATE. This hides the implementation of the interior of the AGGREGATE from any external client, while giving the root responsibility for ensuring the integrity of the AGGREGATE as elements are added, as shown in Figure 6.13 on the next page.
图 6.13。工厂方法封装了聚合的扩展。
Figure 6.13. A FACTORY METHOD encapsulates expansion of an AGGREGATE.
另一个例子是将工厂方法放在一个与生成另一个对象密切相关的对象上,尽管一旦创建产品,它就不拥有该产品。当一个对象的数据和可能的规则在创建另一个对象时非常占主导地位时,对象,这样可以节省从生成器中提取信息的时间,以便在其他地方使用这些信息来创建对象。它还传达了生成器和产品之间的特殊关系。
Another example would be to place a FACTORY METHOD on an object that is closely involved in spawning another object, although it doesn’t own the product once it is created. When the data and possibly the rules of one object are very dominant in the creation of an object, this saves pulling information out of the spawner to be used elsewhere to create the object. It also communicates the special relationship between the spawner and the product.
在图 6.14中,交易订单与经纪账户不属于同一个聚合 (AGGREGATE),因为首先,它将继续与交易执行应用程序交互,而经纪账户只会妨碍它。即便如此,让经纪账户控制交易订单的创建似乎也很自然。经纪账户包含将嵌入交易订单中的信息(从其自己的身份开始),并且包含管理允许哪些交易的规则。隐藏交易订单的实现也可能对我们有益。例如,它可以重构为一个层次结构,为买入订单和卖出订单提供单独的子类。工厂 (FACTORY)使客户端不与具体类耦合。
In Figure 6.14, the Trade Order is not part of the same AGGREGATE as the Brokerage Account because, for a start, it will go on to interact with the trade execution application, where the Brokerage Account would only be in the way. Even so, it seems natural to give the Brokerage Account control over the creation of Trade Orders. The Brokerage Account contains information that will be embedded in the Trade Order (starting with its own identity), and it contains rules that govern what trades are allowed. We might also benefit from hiding the implementation of Trade Order. For example, it might be refactored into a hierarchy, with separate subclasses for Buy Order and Sell Order. The FACTORY keeps the client from being coupled to the concrete classes.
图 6.14。工厂方法生成不属于同一聚合的实体。
Figure 6.14. A FACTORY METHOD spawns an ENTITY that is not part of the same AGGREGATE.
FACTORY与其产品的耦合度非常高,因此FACTORY应该只附加到与产品有密切自然关系的对象上。当我们想要隐藏某些东西 — — 无论是具体的实现还是纯粹的构造复杂性 — — 但又似乎没有一个自然的宿主时,我们必须创建专用的FACTORY对象或SERVICE。独立的FACTORY通常会生成整个AGGREGATE,分发对根的引用,并确保执行产品AGGREGATE 的不变量。如果AGGREGATE内部的对象需要FACTORY,并且AGGREGATE根不是它的合理归宿,那么请继续创建独立的FACTORY 。但要遵守限制AGGREGATE内访问的规则,并确保只有来自 AGGREGATE 外部对产品的瞬时引用。
A FACTORY is very tightly coupled to its product, so a FACTORY should be attached only to an object that has a close natural relationship with the product. When there is something we want to hide—either the concrete implementation or the sheer complexity of construction—yet there doesn’t seem to be a natural host, we must create a dedicated FACTORY object or SERVICE. A standalone FACTORY usually produces an entire AGGREGATE, handing out a reference to the root, and ensuring that the product AGGREGATE’S invariants are enforced. If an object interior to an AGGREGATE needs a FACTORY, and the AGGREGATE root is not a reasonable home for it, then go ahead and make a standalone FACTORY. But respect the rules limiting access within an AGGREGATE, and make sure there are only transient references to the product from outside the AGGREGATE.
图 6.15。 独立FACTORY构建AGGREGATE。
Figure 6.15. A standalone FACTORY builds AGGREGATE.
我见过太多的代码,其中所有实例都是通过直接调用类构造函数或编程语言中实例创建的原始级别来创建的。引入FACTORIES有很大的优势,但通常没有得到充分利用。然而,有时构造函数的直接性使其成为最佳选择。F ACTORIES实际上可以掩盖不使用多态性的简单对象。
I’ve seen far too much code in which all instances are created by directly calling class constructors, or whatever the primitive level of instance creation is for the programming language. The introduction of FACTORIES has great advantages, and is generally underused. Yet there are times when the directness of a constructor makes it the best choice. FACTORIES can actually obscure simple objects that don’t use polymorphism.
在下列情况下,权衡利弊有利于使用裸的公共构造函数。
The trade-offs favor a bare, public constructor in the following circumstances.
• 类是类型。它不属于任何有趣的层次结构,也不能通过实现接口来多态地使用。
• The class is the type. It is not part of any interesting hierarchy, and it isn’t used polymorphically by implementing an interface.
• 客户关心实施情况,或许将其作为选择策略的一种方式。
• The client cares about the implementation, perhaps as a way of choosing a STRATEGY.
•对象的所有属性均可供客户端使用,因此没有任何对象创建会嵌套在向客户端公开的构造函数内。
• All of the attributes of the object are available to the client, so that no object creation gets nested inside the constructor exposed to the client.
• 构造并不复杂。
• The construction is not complicated.
• 公共构造函数必须遵循与FACTORY相同的规则:它必须是满足所创建对象的所有不变量的原子操作。
• A public constructor must follow the same rules as a FACTORY: It must be an atomic operation that satisfies all invariants of the created object.
避免在其他类的构造函数中调用构造函数。构造函数应该非常简单。复杂的程序集(尤其是AGGREGATES的程序集)需要调用FACTORIES 。选择使用小型FACTORY METHOD的门槛并不高。
Avoid calling constructors within constructors of other classes. Constructors should be dead simple. Complex assemblies, especially of AGGREGATES, call for FACTORIES. The threshold for choosing to use a little FACTORY METHOD isn’t high.
Java 类库提供了有趣的示例。所有集合都实现了将客户端与具体实现分离的接口。但它们都是通过直接调用构造函数创建的。FACTORY可以封装集合层次结构。FACTORY的方法可以允许客户端请求所需的功能,而 FACTORY 可以选择要实例化的适当类。创建集合的代码将更具表现力,并且可以安装新的集合类而不会破坏每个 Java 程序。
The Java class library offers interesting examples. All collections implement interfaces that decouple the client from the concrete implementation. Yet they are all created by direct calls to constructors. A FACTORY could have encapsulated the collection hierarchy. The FACTORY’s methods could have allowed a client to ask for the features it needed, with the FACTORY selecting the appropriate class to instantiate. Code that created collections would be more expressive, and new collection classes could be installed without breaking every Java program.
但是,有理由支持使用具体构造函数。首先,对于许多应用程序来说,实现的选择可能对性能很敏感,因此应用程序可能需要控制。(即便如此,真正智能的FACTORY可以适应这些因素。)无论如何,集合类并不多,因此选择并不复杂。
But there is a case in favor of the concrete constructors. First, the choice of implementation can be performance sensitive for many applications, so an application might want control. (Even so, a really smart FACTORY could accommodate such factors.) Anyway, there aren’t very many collection classes, so it isn’t that complicated to choose.
由于使用模式的原因,抽象集合类型虽然没有 FACTORY,但仍保留了一些价值。集合通常在一个地方创建,在另一个地方使用。这意味着最终使用集合(添加、移除和检索其内容)的客户端仍然可以与接口通信,并与实现分离。集合类的选择通常由拥有集合的对象或拥有对象的FACTORY决定。
The abstract collection types preserve some value in spite of the lack of a FACTORY because of their usage patterns. Collections are very often created in one place and used in another. This means that the client that ultimately uses the collection—adding, removing, and retrieving its contents—can still talk to the interface and be decoupled from the implementation. The selection of a collection class typically falls to the object that owns the collection, or to the owning object’s FACTORY.
当设计一个FACTORY的方法签名时,无论是独立的还是FACTORY METHOD,都要记住这两点。
When designing the method signature of a FACTORY, whether standalone or FACTORY METHOD, keep in mind these two points.
•每个操作都必须是原子的。您必须在与 FACTORY 的一次交互中传入创建完整产品所需的一切。您还必须决定如果创建失败(某些不变量不满足)会发生什么。您可以抛出异常或仅返回 null。为了保持一致,请考虑采用FACTORIES中失败的编码标准。
• Each operation must be atomic. You have to pass in everything needed to create a complete product in a single interaction with the FACTORY. You also have to decide what will happen if creation fails, in the event that some invariant isn’t satisfied. You could throw an exception or just return a null. To be consistent, consider adopting a coding standard for failures in FACTORIES.
• FACTORY将与其参数耦合。如果您在选择输入参数时不小心,可能会产生依赖关系的混乱局面。耦合程度取决于您对参数的处理方式。如果只是将其插入产品,那么您就创建了适度的依赖关系。如果您从参数中挑选部分用于构造,耦合会变得更紧密。
• The FACTORY will be coupled to its arguments. If you are not careful in your selection of input parameters, you can create a rat’s nest of dependencies. The degree of coupling will depend on what you do with the argument. If it is simply plugged into the product, you’ve created a modest dependency. If you are picking parts out of the argument to use in the construction, the coupling gets tighter.
最安全的参数是来自较低设计层的参数。即使在一个层内,也往往存在自然层,其中包含更多基本对象,这些对象由较高级别的对象使用。(此类分层将在第10 章“柔性设计”和第 16 章“大型结构”中以不同方式讨论。)
The safest parameters are those from a lower design layer. Even within a layer, there tend to be natural strata with more basic objects that are used by higher level objects. (Such layering will be discussed in different ways in Chapter 10, “Supple Design,” and again in Chapter 16, “Large-Scale Structure.”)
另一个不错的参数选择是与模型中的产品紧密相关的对象,这样就不会添加新的依赖关系。在前面的Purchase Order Item示例中,FACTORY METHOD将Catalog Part作为参数,这是Item的基本关联。这在Purchase Order类和Part之间添加了直接依赖关系。但这三个对象形成了一个紧密的概念组。无论如何,Purchase Order 的 AGGREGATE已经引用了Part 。因此,将控制权交给AGGREGATE根并封装AGGREGATE 的内部结构是一种很好的权衡。
Another good choice of parameter is an object that is closely related to the product in the model, so that no new dependency is being added. In the earlier example of a Purchase Order Item, the FACTORY METHOD takes a Catalog Part as an argument, which is an essential association for the Item. This adds a direct dependency between the Purchase Order class and the Part. But these three objects form a close conceptual group. The Purchase Order’s AGGREGATE already referenced the Part, anyway. So giving control to the AGGREGATE root and encapsulating the AGGREGATE’S internal structure is a good trade-off.
使用参数的抽象类型,而不是它们的具体类。FACTORY与产品的具体类耦合;它不需要也与具体参数耦合。
Use the abstract type of the arguments, not their concrete classes. The FACTORY is coupled to the concrete class of the products; it does not need to be coupled to concrete parameters also.
FACTORY负责确保其创建的对象或聚合满足所有不变量;但在删除适用于该对象之外的对象的规则之前,您应该三思而后行。FACTORY可以将不变量检查委托给产品,这通常是最好的选择。
A FACTORY is responsible for ensuring that all invariants are met for the object or AGGREGATE it creates; yet you should always think twice before removing the rules applying to an object outside that object. The FACTORY can delegate invariant checking to the product, and this is often best.
但是工厂与产品有着特殊的关系。它们已经了解产品的内部结构,它们存在的全部原因都与产品的实现有关。在某些情况下,将不变逻辑放在工厂中并减少产品中的混乱是有好处的。这对于AGGREGATE规则(跨多个对象)尤其有吸引力。对于附加到其他域对象的工厂方法来说,这尤其没有吸引力。
But FACTORIES have a special relationship with their products. They already know their product’s internal structure, and their entire reason for being involves the implementation of their product. Under some circumstances, there are advantages to placing invariant logic in the FACTORY and reducing clutter in the product. This is especially appealing with AGGREGATE rules (which span many objects). It is especially unappealing with FACTORY METHODS attached to other domain objects.
尽管原则上不变量在每个操作结束时都适用,但通常允许对对象进行的转换永远不会使它们发挥作用。可能存在适用于 ENTITY 身份属性分配的规则。但在创建之后,该身份是不可变的。V ALUE OBJECTS是完全不可变的。对象不需要携带在其活动生命周期内永远不会应用的逻辑。在这种情况下,FACTORY是放置不变量的合理位置,可使产品更简单。
Although in principle invariants apply at the end of every operation, often the transformations allowed to the object can never bring them into play. There might be a rule that applies to the assignment of the identity attributes of an ENTITY. But after creation that identity is immutable. VALUE OBJECTS are completely immutable. An object doesn’t need to carry around logic that will never be applied in its active lifetime. In such cases, the FACTORY is a logical place to put invariants, keeping the product simpler.
ENTITY FACTORIES与VALUE OBJECT FACTORIES有两点不同。VALUE OBJECTS是不可变的;产品以最终形式呈现。因此,FACTORY操作必须允许对产品进行完整描述。ENTITY FACTORIES往往只采用构成有效AGGREGATE所需的基本属性。如果不变式不需要详细信息,则可以稍后添加。
ENTITY FACTORIES differ from VALUE OBJECT FACTORIES in two ways. VALUE OBJECTS are immutable; the product comes out complete in its final form. So the FACTORY operations have to allow for a full description of the product. ENTITY FACTORIES tend to take just the essential attributes required to make a valid AGGREGATE. Details can be added later if they are not required by an invariant.
然后,还有为ENTITY分配身份时涉及的问题— 与VALUE OBJECT无关。如第 5 章所述,标识符可以由程序自动分配,也可以从外部提供,通常由用户提供。如果要通过电话号码跟踪客户的身份,那么显然必须将该电话号码作为参数传递给 FACTORY 。当程序分配标识符时,FACTORY是控制尽管唯一跟踪 ID 的实际生成通常是由数据库“序列”或其他基础设施机制完成的,但 FACTORY知道要请求什么以及将其放在哪里。
Then there are the issues involved in assigning identity to an ENTITY—irrelevant to a VALUE OBJECT. As pointed out in Chapter 5, an identifier can either be assigned automatically by the program or supplied from the outside, typically by the user. If a customer’s identity is to be tracked by the telephone number, then that telephone number must obviously be passed in as an argument to the FACTORY. When the program is assigning an identifier, the FACTORY is a good place to control it. Although the actual generation of a unique tracking ID is typically done by a database “sequence” or other infrastructure mechanism, the FACTORY knows what to ask for and where to put it.
到目前为止,FACTORY在对象生命周期的最初阶段发挥了作用。在某个时候,大多数对象都会被存储在数据库中或通过网络传输,而目前很少有数据库技术能够保留其内容的对象特征。大多数传输方法将对象扁平化为更有限的呈现方式。因此,检索需要将各个部分重新组装成一个活动对象的潜在复杂过程。
Up to this point, the FACTORY has played its part in the very beginning of an object’s life cycle. At some point, most objects get stored in databases or transmitted through a network, and few current database technologies retain the object character of their contents. Most transmission methods flatten an object into an even more limited presentation. Therefore, retrieval requires a potentially complex process of reassembling the parts into a live object.
用于重建的 FACTORY与用于创建的 FACTORY 非常相似,但有两个主要区别。
A FACTORY used for reconstitution is very similar to one used for creation, with two major differences.
1.用于重构的 ENTITY FACTORY不会分配新的跟踪 ID。这样做会失去与对象前一个版本的连续性。因此,识别属性必须是重构存储对象的 FACTORY 中的输入参数的一部分。
1. An ENTITY FACTORY used for reconstitution does not assign a new tracking ID. To do so would lose the continuity with the object’s previous incarnation. So identifying attributes must be part of the input parameters in a FACTORY reconstituting a stored object.
2. 工厂重构对象时,将以不同的方式处理不变量违反问题。在创建新对象时,如果不满足不变量,工厂应该直接拒绝,但在重构时可能需要更灵活的响应。如果对象已存在于系统中的某个位置(例如数据库中),则不能忽略这一事实。但我们也不能忽略规则违反问题。必须有某种策略来修复此类不一致,这会使重构比创建新对象更具挑战性。
2. A FACTORY reconstituting an object will handle violation of an invariant differently. During creation of a new object, a FACTORY should simply balk when an invariant isn’t met, but a more flexible response may be necessary in reconstitution. If an object already exists somewhere in the system (such as in the database), this fact cannot be ignored. Yet we also can’t ignore the rule violation. There has to be some strategy for repairing such inconsistencies, which can make reconstitution more challenging than the creation of new objects.
图 6.16和6.17(见下页)显示了两种类型的重构。对象映射技术可以在数据库重构的情况下提供部分或全部这些服务,这很方便。每当从另一种介质重构对象时暴露出复杂性时,FACTORY都是一个不错的选择。
Figures 6.16 and 6.17 (on the next page) show two kinds of reconstitution. Object-mapping technologies may provide some or all of these services in the case of database reconstitution, which is convenient. Whenever there is exposed complexity in reconstituting an object from another medium, the FACTORY is a good option.
图 6.16. 重构从关系数据库检索到的ENTITY
Figure 6.16. Reconstituting an ENTITY retrieved from a relational database
图 6.17. 重构以 XML 形式传输的ENTITY
Figure 6.17. Reconstituting an ENTITY transmitted as XML
总而言之,必须确定创建实例的访问点,并明确定义其范围。它们可能只是构造函数,但通常需要更抽象或精心设计的实例创建机制。这种需求在设计中引入了新的构造:工厂。工厂通常不表达模型的任何部分,但它们是领域设计的一部分,有助于保持表达模型的对象清晰。
To sum up, the access points for creation of instances must be identified, and their scope must be defined explicitly. They may simply be constructors, but often there is a need for a more abstract or elaborate instance creation mechanism. This need introduces new constructs into the design: FACTORIES. FACTORIES usually do not express any part of the model, yet they are a part of the domain design that helps keep the model-expressing objects sharp.
FACTORY封装了创建和重构的生命周期转换。另一个暴露技术复杂性的转换是存储转换,这可能会淹没域设计。此转换是另一个域设计构造 REPOSITORY的责任。
A FACTORY encapsulates the life cycle transitions of creation and reconstitution. Another transition that exposes technical complexity that can swamp the domain design is the transition to and from storage. This transition is the responsibility of another domain design construct, the REPOSITORY.
关联允许我们根据对象与另一个对象的关系来查找对象。但是,我们必须在 ENTITY 或 VALUE的生命周期中有一个遍历到该ENTITY或VALUE的起点。
Associations allow us to find an object based on its relationship to another. But we must have a starting point for a traversal to an ENTITY or VALUE in the middle of its life cycle.
要对对象执行任何操作,您必须持有对它的引用。如何获取该引用?一种方法是创建对象,因为创建操作将返回对新对象的引用。第二种方法是遍历关联。您从已知的对象开始,并向其请求关联对象。任何面向对象的程序都会做很多这样的事情,这些链接赋予对象模型很大的表达能力。但您必须获取第一个对象。
To do anything with an object, you have to hold a reference to it. How do you get that reference? One way is to create the object, as the creation operation will return a reference to the new object. A second way is to traverse an association. You start with an object you already know and ask it for an associated object. Any object-oriented program is going to do a lot of this, and these links give object models much of their expressive power. But you have to get that first object.
我曾经遇到过一个项目,团队在热情拥抱模型驱动设计的过程中,试图通过创建或遍历来完成所有对象访问!他们的对象驻留在对象数据库中,他们推断现有的概念关系将提供所有必要的关联。他们只需要对其进行足够的分析,使整个领域模型具有凝聚力。这种自我强加的限制迫使他们创造了一种无休止的纠结,我们在过去几章中一直试图避免这种纠结,通过谨慎实施ENTITIES和应用AGGREGATES。团队成员并没有坚持这种策略很长时间,但他们从未用另一种连贯的方法取而代之。他们拼凑了临时解决方案,变得不那么雄心勃勃。
I actually encountered a project once in which the team was attempting, in an enthusiastic embrace of MODEL-DRIVEN DESIGN, to do all object access by creation or traversal! Their objects resided in an object database, and they reasoned that existing conceptual relationships would provide all necessary associations. They needed only to analyze them enough, making their entire domain model cohesive. This self-imposed limitation forced them to create just the kind of endless tangle that we have been trying to avert over the last few chapters, with careful implementation of ENTITIES and application of AGGREGATES. The team members didn’t stick with this strategy long, but they never replaced it with another coherent approach. They cobbled together ad hoc solutions and became less ambitious.
很少有人会想到这种方法,更不用说被它所诱惑了,因为他们将大多数对象存储在关系数据库中。这种存储技术使得使用第三种获取引用的方式变得很自然:根据对象的属性执行查询以在数据库中查找对象,或者查找对象的组成部分然后重新构建它。
Few would even think of this approach, much less be tempted by it, because they store most of their objects in relational databases. This storage technology makes it natural to use the third way of getting a reference: Execute a query to find the object in a database based on its attributes, or find the constituents of an object and then reconstitute it.
数据库搜索是全局可访问的,可以直接转到任何对象。不需要所有对象都互连,这使我们能够保持对象网络的可管理性。是否提供遍历或依赖于搜索成为设计决策,在搜索的解耦与关联的内聚性之间进行权衡。客户对象是否应该保存所有下达的订单的集合?还是应该在数据库中找到订单,并在客户 ID 字段上进行搜索?搜索和关联的正确组合使设计变得易于理解。
A database search is globally accessible and makes it possible to go directly to any object. There is no need for all objects to be interconnected, which allows us to keep the web of objects manageable. Whether to provide a traversal or depend on a search becomes a design decision, trading off the decoupling of the search against the cohesiveness of the association. Should the Customer object hold a collection of all the Orders placed? Or should the Orders be found in the database, with a search on the Customer ID field? The right combination of search and association makes the design comprehensible.
不幸的是,开发人员通常不会过多考虑这些设计细节,因为他们正沉浸于存储对象、将其取回(并最终将其从存储中删除)所需的大量机制之中。
Unfortunately, developers don’t usually get to think much about such design subtleties, because they are swimming in the sea of mechanisms needed to pull off the trick of storing an object and bringing it back—and eventually removing it from storage.
现在从技术角度来看,检索存储的对象实际上是创建的一个子集,因为数据库中的数据用于组装新对象。事实上,通常必须编写的代码很难让人忘记这一现实。但从概念上讲,这是ENTITY生命周期的中间阶段。 Customer 对象并不代表新客户,只是因为我们将其存储在数据库中并检索了它。为了记住这一区别,我将从存储数据中创建实例称为重建。
Now from a technical point of view, retrieval of a stored object is really a subset of creation, because the data from the database is used to assemble new objects. Indeed, the code that usually has to be written makes it hard to forget this reality. But conceptually, this is the middle of the life cycle of an ENTITY. A Customer object does not represent a new customer just because we stored it in a database and retrieved it. To keep this distinction in mind, I refer to the creation of an instance from stored data as reconstitution.
领域驱动设计的目标是通过关注领域模型而不是技术来创建更好的软件。当开发人员构建 SQL 查询、将其传递给基础架构层的查询服务、获取表行结果集、提取必要信息并将其传递给构造函数或FACTORY时,模型焦点已经消失。人们很自然地会将对象视为查询提供的数据的容器,整个设计转向数据处理风格。技术细节各不相同,但问题仍然是客户端处理的是技术,而不是模型概念。诸如元数据映射层(Fowler 2003) 之类的基础设施有很大帮助,因为它们使查询结果更容易转换为对象,但开发人员仍然在考虑技术机制,而不是领域。更糟糕的是,由于客户端代码直接使用数据库,开发人员倾向于绕过诸如聚合 (AGGREGATES)甚至对象封装之类的模型功能,而是直接获取和操作他们需要的数据。越来越多的领域规则嵌入查询代码中或干脆丢失。对象数据库确实消除了转换问题,但搜索机制通常仍然是机械的,开发人员仍然倾向于抓取他们想要的任何对象。
The goal of domain-driven design is to create better software by focusing on a model of the domain rather than the technology. By the time a developer has constructed an SQL query, passed it to a query service in the infrastructure layer, obtained a result set of table rows, pulled the necessary information out, and passed it to a constructor or FACTORY, the model focus is gone. It becomes natural to think of the objects as containers for the data that the queries provide, and the whole design shifts toward a data-processing style. The details of the technology vary, but the problem remains that the client is dealing with technology, rather than model concepts. Infrastructure such as METADATA MAPPING LAYERS (Fowler 2003) help a great deal, by making easier the conversion of the query result into objects, but the developer is still thinking about technical mechanisms, not the domain. Worse, as client code uses the database directly, developers are tempted to bypass model features such as AGGREGATES, or even object encapsulation, instead directly taking and manipulating the data they need. More and more domain rules become embedded in query code or simply lost. Object databases do eliminate the conversion problem, but search mechanisms are usually still mechanistic, and developers are still tempted to grab whatever objects they want.
客户端需要一种实用的方法来获取对预先存在的域对象的引用。如果基础架构使这样做变得容易,客户端的开发人员可能会添加更多可遍历的关联,从而使模型变得混乱。另一方面,他们可以使用查询从数据库中提取所需的精确数据,或者提取一些特定对象,而不是从AGGREGATE根导航。域逻辑转移到查询和客户端代码中,而ENTITIES和VALUE OBJECTS则成为单纯的数据容器。应用大多数数据库访问基础架构的纯粹技术复杂性很快淹没了客户端代码,这导致开发人员简化域层,从而使模型变得无关紧要。
A client needs a practical means of acquiring references to preexisting domain objects. If the infrastructure makes it easy to do so, the developers of the client may add more traversable associations, muddling the model. On the other hand, they may use queries to pull the exact data they need from the database, or to pull a few specific objects rather than navigating from AGGREGATE roots. Domain logic moves into queries and client code, and the ENTITIES and VALUE OBJECTS become mere data containers. The sheer technical complexity of applying most database access infrastructure quickly swamps the client code, which leads developers to dumb down the domain layer, which makes the model irrelevant.
根据目前讨论的设计原则,我们可以在一定程度上缩小对象访问问题的范围,前提是我们找到一种访问方法,使模型焦点足够清晰,可以应用这些原则。首先,我们不需要关心瞬态对象。瞬态对象(通常是VALUE OBJECTS)的生命周期很短,用于创建它们的客户端操作,然后被丢弃。我们也不需要查询访问更方便通过遍历找到的持久对象。例如,可以从 Person 对象请求某人的地址。最重要的是,除了从根遍历之外,禁止访问AGGREGATE内部的任何对象。
Drawing on the design principles discussed so far, we can reduce the scope of the object access problem somewhat, assuming that we find a method of access that keeps the model focus sharp enough to employ those principles. For starters, we need not concern ourselves with transient objects. Transients (typically VALUE OBJECTS) live brief lives, used in the client operation that created them and then discarded. We also need no query access for persistent objects that are more convenient to find by traversal. For example, the address of a person could be requested from the Person object. And most important, any object internal to an AGGREGATE is prohibited from access except by traversal from the root.
持久值对象 通常通过从充当封装它们的AGGREGATE根的某个ENTITY进行遍历来找到它们。实际上,对VALUE 的全局搜索访问通常是没有意义的,因为通过其属性查找VALUE相当于创建具有这些属性的新实例。不过也有例外。例如,当我在线计划旅行时,我有时会保存一些预期的行程,稍后再返回选择其中之一进行预订。这些行程是VALUE(如果有两个由相同的航班组成,我不会在意哪个是哪个),但它们已与我的用户名相关联并完整地为我检索。另一种情况是“枚举”,即类型具有严格限制的、预定的一组可能值。但是,对VALUE OBJECTS的全局访问比对ENTITIES 的全局访问要少见得多,并且如果您发现需要在数据库中搜索预先存在的VALUE,则值得考虑您确实得到了一个您尚未识别其身份的ENTITY的可能性。
Persistent VALUE OBJECTS are usually found by traversal from some ENTITY that acts as the root of the AGGREGATE that encapsulates them. In fact, a global search access to a VALUE is often meaningless, because finding a VALUE by its properties would be equivalent to creating a new instance with those properties. There are exceptions, though. For example, when I am planning travel online, I sometimes save a few prospective itineraries and return later to select one to book. Those itineraries are VALUES (if there were two made up of the same flights, I would not care which was which), but they have been associated with my user name and retrieved for me intact. Another case would be an “enumeration,” when a type has a strictly limited, predetermined set of possible values. Global access to VALUE OBJECTS is much less common than for ENTITIES, though, and if you find you need to search the database for a preexisting VALUE, it is worth considering the possibility that you’ve really got an ENTITY whose identity you haven’t recognized.
从这个讨论中可以清楚地看出,大多数对象不应该通过全局搜索来访问。如果设计能够传达出那些需要访问的对象,那就更好了。
From this discussion, it is clear that most objects should not be accessed by a global search. It would be nice for the design to communicate those that do.
现在可以更准确地重述这个问题。
Now the problem can be restated more precisely.
持久对象的子集必须能够通过基于对象属性的搜索全局访问。对于不方便通过遍历到达的 AGGREGATES 根,需要进行此类访问。它们通常是ENTITIES ,有时是具有复杂内部结构的VALUE OBJECTS,有时是枚举值。提供对其他对象的访问会混淆重要的区别。自由数据库查询实际上可能会破坏域对象和AGGREGATES的封装。技术基础设施和数据库访问机制的暴露使客户端变得复杂,并掩盖了模型驱动设计。
A subset of persistent objects must be globally accessible through a search based on object attributes. Such access is needed for the roots of AGGREGATES that are not convenient to reach by traversal. They are usually ENTITIES, sometimes VALUE OBJECTS with complex internal structure, and sometimes enumerated VALUES. Providing access to other objects muddies important distinctions. Free database queries can actually breach the encapsulation of domain objects and AGGREGATES. Exposure of technical infrastructure and database access mechanisms complicates the client and obscures the MODEL-DRIVEN DESIGN.
有许多技术可以解决数据库访问的技术挑战。例如,将 SQL 封装到查询对象中,或使用元数据映射层在对象和表之间进行转换(Fowler 2003)。工厂可以帮助重建存储的对象(本章后面将讨论)。这些和许多其他技术有助于控制复杂性。
There is a raft of techniques for dealing with the technical challenges of database access. Examples include encapsulating SQL into QUERY OBJECTS or translating between objects and tables with METADATA MAPPING LAYERS (Fowler 2003). FACTORIES can help reconstitute stored objects (as discussed later in this chapter). These and many other techniques help keep a lid on complexity.
但即便如此,也要注意我们失去了什么。我们不再考虑领域模型中的概念。我们的代码将不再沟通业务;它将操纵技术数据检索。REPOSITORY模式是一个简单的概念框架,用于封装这些解决方案并重新聚焦我们的模型。
But even so, take note of what has been lost. We are no longer thinking about concepts in our domain model. Our code will not be communicating about the business; it will be manipulating the technology of data retrieval. The REPOSITORY pattern is a simple conceptual framework to encapsulate those solutions and bring back our model focus.
REPOSITORY将特定类型的所有对象表示为一个概念集(通常是模拟的)。它的作用类似于集合,但具有更复杂的查询功能。适当类型的对象被添加和删除,REPOSITORY 背后的机制将它们插入或从数据库中删除。此定义汇集了一组连贯的职责,用于从生命周期的早期到结束提供对AGGREGATES根的访问。
A REPOSITORY represents all objects of a certain type as a conceptual set (usually emulated). It acts like a collection, except with more elaborate querying capability. Objects of the appropriate type are added and removed, and the machinery behind the REPOSITORY inserts them or deletes them from the database. This definition gathers a cohesive set of responsibilities for providing access to the roots of AGGREGATES from early life cycle through the end.
客户端使用查询方法从REPOSITORY请求对象,这些查询方法根据客户端指定的条件(通常是某些属性的值)选择对象。REPOSITORY检索所请求的对象,封装数据库查询和元数据映射机制。REPOSITORY可以实现各种查询,这些查询根据客户端要求的任何条件选择对象。它们还可以返回摘要信息,例如满足某些条件的实例数。它们甚至可以返回摘要计算,例如某些数值属性的所有匹配对象的总数。
Clients request objects from the REPOSITORY using query methods that select objects based on criteria specified by the client, typically the value of certain attributes. The REPOSITORY retrieves the requested object, encapsulating the machinery of database queries and metadata mapping. REPOSITORIES can implement a variety of queries that select objects based on whatever criteria the client requires. They can also return summary information, such as a count of how many instances meet some criteria. They can even return summary calculations, such as the total across all matching objects of some numerical attribute.
图 6.18。存储库正在搜索客户端
Figure 6.18. A REPOSITORY doing a search for a client
REPOSITORY减轻了客户端的巨大负担,客户端现在可以与一个简单、意图明确的界面进行对话,并根据模型提出需求。要支持所有这些,需要大量复杂的技术基础设施,但界面很简单,并且在概念上与领域模型相连。
A REPOSITORY lifts a huge burden from the client, which can now talk to a simple, intention-revealing interface, and ask for what it needs in terms of the model. To support all this requires a lot of complex technical infrastructure, but the interface is simple and conceptually connected to the domain model.
所以:
Therefore:
对于需要全局访问的每种类型的对象,创建一个对象,该对象可以提供该类型所有对象的内存集合的幻觉。通过众所周知的全局接口设置访问。提供添加和删除对象的方法,这将封装数据存储中实际的数据插入或删除。提供根据某些条件选择对象的方法,并返回完全实例化的对象或属性值符合条件的对象集合,从而封装实际的存储和查询技术。仅为实际需要直接访问的AGGREGATE根提供REPOSITORIES。让客户端专注于模型,将所有对象存储和访问委托给REPOSITORIES。
For each type of object that needs global access, create an object that can provide the illusion of an in-memory collection of all objects of that type. Set up access through a well-known global interface. Provide methods to add and remove objects, which will encapsulate the actual insertion or removal of data in the data store. Provide methods that select objects based on some criteria and return fully instantiated objects or collections of objects whose attribute values meet the criteria, thereby encapsulating the actual storage and query technology. Provide REPOSITORIES only for AGGREGATE roots that actually need direct access. Keep the client focused on the model, delegating all object storage and access to the REPOSITORIES.
R EPOSITORIES具有许多优点,其中包括:
REPOSITORIES have many advantages, including the following:
• 它们为客户提供了一个获取持久对象并管理其生命周期的简单模型。
• They present clients with a simple model for obtaining persistent objects and managing their life cycle.
• 它们将应用程序和领域设计与持久性技术、多种数据库策略甚至多个数据源分离。
• They decouple application and domain design from persistence technology, multiple database strategies, or even multiple data sources.
• 他们传达有关对象访问的设计决策。
• They communicate design decisions about object access.
• 它们允许轻松替换虚拟实现,以用于测试(通常使用内存集合)。
• They allow easy substitution of a dummy implementation, for use in testing (typically using an in-memory collection).
所有存储库都提供了允许客户端请求符合某些条件的对象的方法,但是如何设计此接口有多种选择。
All repositories provide methods that allow a client to request objects matching some criteria, but there is a range of options of how to design this interface.
最容易构建的REPOSITORY具有带特定参数的硬编码查询。这些查询可以是多种多样的:通过其身份检索ENTITY (几乎所有REPOSITORIES都提供此身份);请求具有特定属性值或复杂参数组合的对象集合;根据值范围(例如日期范围)选择对象;甚至执行一些属于 REPOSITORY 一般职责的计算(尤其是利用底层数据库支持的操作)。
The easiest REPOSITORY to build has hard-coded queries with specific parameters. These queries can be various: retrieving an ENTITY by its identity (provided by almost all REPOSITORIES); requesting a collection of objects with a particular attribute value or a complex combination of parameters; selecting objects based on value ranges (such as date ranges); and even performing some calculations that fall within the general responsibility of a REPOSITORY (especially drawing on operations supported by the underlying database).
虽然大多数查询都会返回一个对象或一个对象集合,但它也符合返回某些类型的摘要计算的概念,例如对象计数或模型想要统计的数值属性的总和。
Although most queries return an object or a collection of objects, it also fits within the concept to return some types of summary calculations, such as an object count, or a sum of a numerical attribute that was intended by the model to be tallied.
图 6.19。简单REPOSITORY中的硬编码查询
Figure 6.19. Hard-coded queries in a simple REPOSITORY
硬编码查询可以建立在任何基础设施之上,而且不需要大量的投资,因为它们所做的只是一些客户本来必须做的事情。
Hard-coded queries can be built on top of any infrastructure and without a lot of investment, because they do just what some client would have had to do anyway.
对于查询量很大的项目,可以构建一个REPOSITORY框架,以允许更灵活的查询。这需要熟悉必要技术的工作人员,并且支持性基础设施将提供很大帮助。
On projects with a lot of querying, a REPOSITORY framework can be built that allows more flexible queries. This calls for a staff familiar with the necessary technology and is greatly aided by a supportive infrastructure.
通过框架概括REPOSITORIES的一个特别合适的方法是使用基于SPECIFICATION的查询。SPECIFICATION允许客户端描述(即指定)它想要什么,而不必担心如何获得它。在此过程中,会创建一个可以实际执行选择的对象。第 9 章将深入讨论此模式。
One particularly apt approach to generalizing REPOSITORIES through a framework is to use SPECIFICATION-based queries. A SPECIFICATION allows a client to describe (that is, specify) what it wants without concern for how it will be obtained. In the process, an object that can actually carry out the selection is created. This pattern will be discussed in depth in Chapter 9.
图 6.20。复杂REPOSITORY中搜索条件的灵活、声明性规范
Figure 6.20. A flexible, declarative SPECIFICATION of search criteria in a sophisticated REPOSITORY
基于SPECIFICATION的查询优雅而灵活。根据可用的基础设施,它可能是一个适度的框架,也可能非常困难。Rob Mee 和 Edward Hieatt在 Fowler 2003 中讨论了设计此类REPOSITORIES所涉及的更多技术问题。
The SPECIFICATION-based query is elegant and flexible. Depending on the infrastructure available, it may be a modest framework or it may be prohibitively difficult. Rob Mee and Edward Hieatt discuss more of the technical issues involved in designing such REPOSITORIES in Fowler 2003.
即使是具有灵活查询的REPOSITORY设计也应该允许添加专门的硬编码查询。它们可能是封装常用查询的便捷方法,或者不返回对象本身的查询,例如数学所选对象的摘要。不允许此类意外情况的框架往往会扭曲领域设计或被开发人员绕过。
Even a REPOSITORY design with flexible queries should allow for the addition of specialized hard-coded queries. They might be convenience methods that encapsulate an often-used query or a query that doesn’t return the objects themselves, such as a mathematical summary of selected objects. Frameworks that don’t allow for such contingencies tend to distort the domain design or get bypassed by developers.
持久性技术的封装使客户端变得非常简单,完全与REPOSITORY的实现脱钩。但正如封装通常的情况一样,开发人员必须了解底层发生了什么。当REPOSITORIES以不同的方式使用或以不同的方式工作时,性能影响可能会非常大。
Encapsulation of the persistence technology allows the client to be very simple, completely decoupled from the implementation of the REPOSITORY. But as is often the case with encapsulation, the developer must understand what is happening under the hood. The performance implications can be extreme when REPOSITORIES are used in different ways or work in different ways.
Kyle Brown 向我讲述了一个故事,他接到了一个基于 WebSphere 的制造应用程序的电话,该应用程序正在投入生产。使用几个小时后,系统莫名其妙地耗尽了内存。Kyle 浏览了代码并找到了原因:有一次,他们正在汇总有关工厂中每件物品的一些信息。开发人员使用名为“所有对象”的查询完成了这项工作,该查询实例化每个对象,然后选择他们需要的部分。此代码的作用是将整个数据库一次性带入内存!由于测试数据量很少,因此问题没有在测试中出现。
Kyle Brown told me the story of getting called in on a manufacturing application based on WebSphere that was being rolled out to production. The system was mysteriously running out of memory after a few hours of use. Kyle browsed through the code and found the reason: At one point, they were summarizing some information about every item in the plant. The developers had done this using a query called “all objects,” which instantiated each of the objects and then selected the bits they needed. This code had the effect of bringing the entire database into memory at once! The problem hadn’t shown up in testing because of the small amount of test data.
这显然是不行的,但更细微的疏忽也会带来同样严重的问题。开发人员需要了解使用封装行为的含义。这并不意味着必须详细熟悉实现。设计良好的组件是可以描述的。(这是第10 章“柔性设计”的要点之一。)
This is an obvious no-no, but much more subtle oversights can present equally serious problems. Developers need to understand the implications of using encapsulated behavior. That does not have to mean detailed familiarity with the implementation. Well-designed components can be characterized. (This is one of the main points of Chapter 10, “Supple Design.”)
正如第 5 章所讨论的,底层技术可能会限制您的建模选择。例如,关系数据库可以对深度组合对象结构施加实际限制。同样,在使用 REPOSITORY和实现其查询之间,必须向开发人员提供双向反馈。
As was discussed in Chapter 5, the underlying technology may constrain your modeling choices. For example, a relational database can place a practical limit on deep compositional object structures. In just the same way, there must be feedback to developers in both directions between the use of the REPOSITORY and the implementation of its queries.
实现方式会有很大差异,具体取决于用于持久性的技术和您拥有的基础设施。理想的做法是将所有内部工作隐藏在客户端之外(尽管客户端的开发人员不会看到),这样无论数据存储在对象数据库中、关系数据库中还是仅保存在内存中,客户端代码都是相同的。REPOSITORY将委托适当的基础设施服务来完成这项工作。封装存储、检索和查询机制是 REPOSITORY实现的最基本功能。
Implementation will vary greatly, depending on the technology being used for persistence and the infrastructure you have. The ideal is to hide all the inner workings from the client (although not from the developer of the client), so that client code will be the same whether the data is stored in an object database, stored in a relational database, or simply held in memory. The REPOSITORY will delegate to the appropriate infrastructure services to get the job done. Encapsulating the mechanisms of storage, retrieval, and query is the most basic feature of a REPOSITORY implementation.
图 6.21。REPOSITORY封装了底层数据存储。
Figure 6.21. The REPOSITORY encapsulates the underlying data store.
REPOSITORY概念适用于多种情况。实现的可能性多种多样,我只能列出一些需要注意的问题。
The REPOSITORY concept is adaptable to many situations. The possibilities of implementation are so diverse that I can only list some concerns to keep in mind.
•抽象类型。REPOSITORY “包含”特定类型的所有实例,但这并不意味着每个类都需要一个REPOSITORY。类型可以是层次结构的抽象超类(例如,TradeOrder可以是BuyOrder或Sell -Order)。类型可以是其实现者甚至不具有层次关系的接口。或者它可以是特定的具体类。请记住,您很可能会面临由于数据库技术缺乏这种多态性而带来的限制。
• Abstract the type. A REPOSITORY “contains” all instances of a specific type, but this does not mean that you need one REPOSITORY for each class. The type could be an abstract superclass of a hierarchy (for example, a TradeOrder could be a BuyOrder or a Sell-Order). The type could be an interface whose implementers are not even hierarchically related. Or it could be a specific concrete class. Keep in mind that you may well face constraints imposed by the lack of such polymorphism in your database technology.
•利用与客户端的分离。与客户端直接调用机制相比,您可以更自由地更改REPOSITORY的实现。您可以利用这一点来优化性能,方法是改变查询技术或在内存中缓存对象,随时自由切换持久性策略。您可以通过提供易于操作的虚拟内存策略来促进对客户端代码和域对象的测试。
• Take advantage of the decoupling from the client. You have more freedom to change the implementation of a REPOSITORY than you would if the client were calling the mechanisms directly. You can take advantage of this to optimize for performance, by varying the query technique or by caching objects in memory, freely switching persistence strategies at any time. You can facilitate testing of the client code and the domain objects by providing an easily manipulated, dummy in-memory strategy.
•将事务控制权留给客户端。虽然REPOSITORY会插入和删除数据库,但它通常不会提交任何内容。例如,保存后提交很诱人,但客户端可能具有正确启动和提交工作单元的上下文。如果REPOSITORY不插手,事务管理将更简单。
• Leave transaction control to the client. Although the REPOSITORY will insert into and delete from the database, it will ordinarily not commit anything. It is tempting to commit after saving, for example, but the client presumably has the context to correctly initiate and commit units of work. Transaction management will be simpler if the REPOSITORY keeps its hands off.
通常,团队会向基础架构层添加一个框架来支持REPOSITORIES的实现。除了与较低级别基础架构组件的协作之外,REPOSITORY超类还可能实现一些基本查询,尤其是在实现灵活查询时。不幸的是,对于 Java 之类的类型系统,这种方法会迫使您将返回的对象类型化为“对象”,让客户端将它们转换为REPOSITORY包含的类型。但当然,这必须通过 Java 中返回集合的查询来完成。
Typically teams add a framework to the infrastructure layer to support the implementation of REPOSITORIES. In addition to the collaboration with the lower level infrastructure components, the REPOSITORY superclass might implement some basic queries, especially when a flexible query is being implemented. Unfortunately, with a type system such as Java’s, this approach would force you to type returned objects as “Object,” leaving the client to cast them to the REPOSITORY’S contained type. But of course, this will have to be done with queries that return collections anyway in Java.
在 Fowler (2003) 中可以找到一些关于实施REPOSITORIES及其一些支持技术模式(如QUERY OBJECT)的额外指导。
Some additional guidance on implementing REPOSITORIES and some of their supporting technical patterns such as QUERY OBJECT can be found in Fowler (2003).
在实现REPOSITORY之类的东西之前,您需要仔细考虑您所承诺的基础设施,尤其是任何架构框架。您可能会发现该框架提供了可用于轻松创建 REPOSITORY 的服务,或者您可能会发现该框架会全程与您作对。您可能会发现架构框架已经定义了一个等效的获取持久对象的模式。或者您可能会发现它定义的模式根本不像REPOSITORY 。
Before implementing something like a REPOSITORY, you need to think carefully about the infrastructure you are committed to, especially any architectural frameworks. You may find that the framework provides services you can use to easily create a REPOSITORY, or you may find that the framework fights you all the way. You may discover that the architectural framework has already defined an equivalent pattern of getting persistent objects. Or you may discover that it has defined a pattern that is not like a REPOSITORY at all.
例如,您的项目可能致力于 J2EE。寻找框架与模型驱动设计模式之间的概念相似性(并记住实体 bean与ENTITY不同),您可能选择使用实体 bean 来对应AGGREGATE根。J2EE 架构框架中负责提供对这些对象的访问的构造是“EJB Home”。尝试将 EJB Home 打扮成 REPOSITORY可能会导致其他问题。
For example, your project might be committed to J2EE. Looking for conceptual affinities between the framework and the patterns of MODEL-DRIVEN DESIGN (and keeping in mind that an entity bean is not the same thing as an ENTITY), you may have chosen to use entity beans to correspond to AGGREGATE roots. The construct within the architectural framework of J2EE that is responsible for providing access to these objects is the “EJB Home.” Trying to dress up the EJB Home to look like a REPOSITORY could lead to other problems.
总的来说,不要与框架作对。寻找方法保持领域驱动设计的基本原则,当框架与之对立时,放弃具体细节。寻找领域驱动设计的概念与框架中的概念之间的相似性。这是假设您别无选择,只能使用框架。许多 J2EE 项目根本不使用实体 bean。如果您有自由,请选择与您想要使用的设计风格相协调的框架或框架的一部分。
In general, don’t fight your frameworks. Seek ways to keep the fundamentals of domain-driven design and let go of the specifics when the framework is antagonistic. Look for affinities between the concepts of domain-driven design and the concepts in the framework. This is assuming that you have no choice but to use the framework. Many J2EE projects don’t use entity beans at all. If you have the freedom, choose frameworks, or parts of frameworks, that are harmonious with the style of design you want to use.
FACTORY处理对象生命周期的开始阶段;REPOSITORY帮助管理中间阶段和结束阶段。当对象被保存在内存中或存储在对象数据库中时,这很简单。但通常至少有一些对象存储在关系数据库、文件或其他非面向对象的系统中。在这种情况下,必须将检索到的数据重新构建为对象形式。
A FACTORY handles the beginning of an object’s life; a REPOSITORY helps manage the middle and the end. When objects are being held in memory, or stored in an object database, this is straightforward. But typically there is at least some object storage in relational databases, files, or other, non-object-oriented systems. In such cases, the retrieved data must be reconstituted into object form.
因为在这种情况下, REPOSITORY是基于数据创建对象,所以许多人认为 REPOSITORY是一个FACTORY —从技术角度来看确实如此。但将模型放在最前面更有用,并且如前所述,存储对象的重建不是新概念对象的创建。在这个领域驱动的设计视图中,FACTORIES和REPOSITORY具有不同的职责。FACTORY创建新对象;REPOSITORY查找旧对象。REPOSITORY 的客户端应该产生对象在内存中的错觉。对象可能必须重建(是的,可能会创建一个新实例),但它是相同的概念对象,仍处于其生命周期的中间。
Because the REPOSITORY is, in this case, creating objects based on data, many people consider the REPOSITORY to be a FACTORY—indeed it is, from a technical point of view. But it is more useful to keep the model in the forefront, and as mentioned before, the reconstitution of a stored object is not the creation of a new conceptual object. In this domain-driven view of the design, FACTORIES and REPOSITORIES have distinct responsibilities. The FACTORY makes new objects; the REPOSITORY finds old objects. The client of a REPOSITORY should be given the illusion that the objects are in memory. The object may have to be reconstituted (yes, a new instance may be created), but it is the same conceptual object, still in the middle of its life cycle.
可以通过将REPOSITORY 委托给FACTORY来协调这两种观点,FACTORY(在理论上,尽管在实践中很少)也可以用于从头开始创建对象。
These two views can be reconciled by making the REPOSITORY delegate object creation to a FACTORY, which (in theory, though seldom in practice) could also be used to create objects from scratch.
图 6.22。REPOSITORY使用FACTORY来重建预先存在的对象。
Figure 6.22. A REPOSITORY uses a FACTORY to reconstitute a preexisting object.
这种明确的分离还有助于将所有持久性责任从工厂中卸载。工厂的工作是从数据中实例化一个可能很复杂的对象。如果产品是一个新对象,客户端会知道这一点,并将其添加到存储库中,存储库将封装对象在数据库中的存储。
This clear separation also helps by unloading all responsibility for persistence from the FACTORIES. A FACTORY’S job is to instantiate a potentially complex object from data. If the product is a new object, the client will know this and can add it to the REPOSITORY, which will encapsulate the storage of the object in the database.
图 6.23。客户端使用REPOSITORY存储新对象。
Figure 6.23. A client uses a REPOSITORY to store a new object.
促使人们将FACTORY和REPOSITORY结合起来的另一种情况是希望实现“查找或创建”功能,客户端可以描述其想要的对象,如果找不到这样的对象,则会为其提供一个新创建的对象。应避免使用此功能。它最多只能提供一点便利。很多情况下,当区分ENTITIES和VALUE OBJECTS时,它似乎很有用。想要VALUE OBJECT的客户端可以直接访问FACTORY并请求新的对象。通常,新对象和现有对象之间的区别在领域中很重要,而透明地组合它们的框架实际上会使情况变得混乱。
One other case that drives people to combine FACTORY and REPOSITORY is the desire for “find or create” functionality, in which a client can describe an object it wants and, if no such object is found, will be given a newly created one. This function should be avoided. It is a minor convenience at best. A lot of cases in which it seems useful go away when ENTITIES and VALUE OBJECTS are distinguished. A client that wants a VALUE OBJECT can go straight to a FACTORY and ask for a new one. Usually, the distinction between a new object and an existing object is important in the domain, and a framework that transparently combines them will actually muddle the situation.
面向对象软件系统中最常见的非对象组件是关系数据库。这一现实表明,混合范式通常会带来一些问题(参见第 5 章)。但与其他大多数组件相比,数据库与对象模型的关系更为密切。数据库不仅与对象交互,还存储构成对象本身的数据的持久形式。关于将对象映射到关系表以及有效地存储和检索它们的技术挑战,已经有很多文章进行了探讨。最近的讨论可以在 Fowler 2003 中找到。目前已经有相当完善的工具用于创建和管理两者之间的映射。除了技术问题之外,这种不匹配还会对对象模型产生重大影响。
The most common nonobject component of primarily object-oriented software systems is the relational database. This reality presents the usual problems of a mixture of paradigms (see Chapter 5). But the database is more intimately related to the object model than are most other components. The database is not just interacting with the objects; it is storing the persistent form of the data that makes up the objects themselves. A good deal has been written about the technical challenges of mapping objects to relational tables and effectively storing and retrieving them. A recent discussion can be found in Fowler 2003. There are reasonably refined tools for creating and managing mappings between the two. Apart from the technical concerns, this mismatch can have a significant impact on the object model.
常见的情况有三种:
There are three common cases:
1.数据库主要是对象的存储库。
1. The database is primarily a repository for the objects.
2.该数据库是为另一个系统设计的。
2. The database was designed for another system.
3.该数据库是为该系统设计的,但除对象存储外还起到其他作用。
3. The database is designed for this system but serves in roles other than object store.
当数据库模式专门作为对象的存储而创建时,为了使映射保持非常简单,值得接受一些模型限制。在没有其他模式设计要求的情况下,数据库可以构造成在进行更新时使聚合完整性更安全、更高效。从技术上讲,关系表设计不必反映域模型。映射工具足够复杂,可以弥合重大差异。问题是,多个重叠模型太复杂了。许多针对模型驱动设计提出的相同论点(避免单独的分析和设计模型)也适用于这种不匹配。这确实需要牺牲对象模型的丰富性,有时必须在数据库中做出妥协设计(例如选择性反规范化),否则就有可能失去模型和实现的紧密耦合。这种方法不需要简单的单对象/单表映射。根据映射工具的功能,可以实现对象的某些聚合或组合。但至关重要的是,映射必须是透明的,通过检查代码或读取映射工具中的条目即可轻松理解。
When the database schema is being created specifically as a store for the objects, it is worth accepting some model limitations in order to keep the mapping very simple. Without other demands on schema design, the database can be structured to make aggregate integrity safer and more efficient as updates are made. Technically, the relational table design does not have to reflect the domain model. Mapping tools are sophisticated enough to bridge significant differences. The trouble is, multiple overlapping models are just too complicated. Many of the same arguments presented for MODEL-DRIVEN DESIGN—avoiding separate analysis and design models—apply to this mismatch. This does entail some sacrifice in the richness of the object model, and sometimes compromises have to be made in the database design (such as selective denormalization), but to do otherwise is to risk losing the tight coupling of model and implementation. This approach doesn’t require a simplistic one-object/one-table mapping. Depending on the power of the mapping tool, some aggregation or composition of objects may be possible. But it is crucial that the mappings be transparent, easily understandable by inspecting the code or reading entries in the mapping tool.
• 当数据库被视为对象存储时,无论映射工具的功能如何,都不要让数据模型和对象模型相差太大。牺牲一些对象关系的丰富性以保持接近关系模型。如果有助于简化对象映射,则妥协一些正式的关系标准,例如规范化。
• When the database is being viewed as an object store, don’t let the data model and the object model diverge far, regardless of the powers of the mapping tools. Sacrifice some richness of object relationships to keep close to the relational model. Compromise some formal relational standards, such as normalization, if it helps simplify the object mapping.
• 对象系统之外的进程不应访问此类对象存储。它们可能会违反对象强制执行的不变量。此外,它们的访问将锁定在数据模型中,因此在重构对象时很难更改。
• Processes outside the object system should not access such an object store. They could violate the invariants enforced by the objects. Also, their access will lock in the data model so that it is hard to change when the objects are refactored.
另一方面,在许多情况下,数据来自遗留系统或外部系统,而这些系统从未打算用作对象存储。在这种情况下,实际上有两个领域模型共存于同一系统中。第 14 章“维护模型完整性”深入讨论了这个问题。遵循其他系统中隐含的模型可能是有意义的,或者最好使模型完全不同。
On the other hand, there are many cases in which the data comes from a legacy or external system that was never intended as a store of objects. In this situation, there are, in reality, two domain models coexisting in the same system. Chapter 14, “Maintaining Model Integrity,” deals with this issue in depth. It may make sense to conform to the model implicit in the other system, or it may be better to make the model completely distinct.
出现异常的另一个原因是性能。可能需要引入一些奇怪的设计变更来解决执行速度问题。
Another reason for exceptions is performance. Quirky design changes may have to be introduced to solve execution speed problems.
但对于关系数据库作为面向对象域的持久形式的常见情况,简单直接是最好的。表行应该包含一个对象,可能还有 AGGREGATE 中的子公司。表中的外键应转换为对另一个ENTITY对象的引用。有时需要偏离这种简单的直接性,但不应导致完全放弃简单映射的原则。
But for the important common case of a relational database acting as the persistent form of an object-oriented domain, simple directness is best. A table row should contain an object, perhaps along with subsidiaries in an AGGREGATE. A foreign key in the table should translate to a reference to another ENTITY object. The necessity of sometimes deviating from this simple directness should not lead to total abandonment of the principle of simple mappings.
UBIQUITOUS LANGUAGE可以帮助将对象和关系组件绑定到单个模型中。对象中元素的名称和关联应该与关系表的映射。尽管某些映射工具的强大功能可能使这看起来不必要,但关系中的细微差别会引起很多混乱。
The UBIQUITOUS LANGUAGE can help tie the object and relational components together to a single model. The names and associations of elements in the objects should correspond meticulously to those of the relational tables. Although the power of some mapping tools may make this seem unnecessary, subtle differences in relationships will cause a lot of confusion.
重构的传统在对象世界中日益盛行,但实际上并没有对关系数据库设计产生太大影响。此外,严重的数据迁移问题阻碍了频繁的变更。这可能会拖累对象模型的重构,但如果对象模型和数据库模型开始出现分歧,透明度就会很快丧失。
The tradition of refactoring that has increasingly taken hold in the object world has not really affected relational database design much. What’s more, serious data migration issues discourage frequent change. This may create a drag on the refactoring of the object model, but if the object model and the database model start to diverge, transparency can be lost quickly.
最后,即使数据库是专门为您的系统创建的,也有一些理由要采用与对象模型截然不同的架构。其他不会实例化对象的软件也可能使用该数据库。即使对象的行为发生快速变化或发展,数据库也可能需要很少的更改。将两者分开是一种诱人的方法。当团队未能使数据库与模型保持同步时,通常会无意中采取这种方法。如果有意识地选择这种分离,则可以得到一个干净的数据库架构,而不是一个笨拙的、充满妥协、符合去年对象模型的架构。
Finally, there are some reasons to go with a schema that is quite distinct from the object model, even when the database is being created specifically for your system. The database may also be used by other software that will not instantiate objects. The database may require little change, even while the behavior of the objects changes or evolves rapidly. Cutting the two loose from each other is a seductive path. It is often taken unintentionally, when the team fails to keep the database current with the model. If the separation is chosen consciously, it can result in a clean database schema—not an awkward one full of compromises conforming to last year’s object model.
前三章介绍了一种模式语言,用于完善模型的细节并维护严密的模型驱动设计。在前面的例子中,模式大多是一次应用一种,但在实际项目中,您必须将它们结合起来。本章提供了一个详细的例子(当然,仍然比实际项目简单得多)。该示例将逐步完成一系列模型和设计改进,假设一个团队处理需求和实施问题并开发模型驱动设计,展示适用的力量以及第二部分的模式如何解决这些问题。
The preceding three chapters introduced a pattern language for honing the fine detail of a model and maintaining a tight MODEL-DRIVEN DESIGN. In the earlier examples, the patterns were mostly applied one at a time, but on a real project you have to combine them. This chapter presents one elaborate example (still drastically simpler than a real project, of course). The example will step through a succession of model and design refinements as a hypothetical team deals with requirements and implementation issues and develops a MODEL-DRIVEN DESIGN, showing the forces that apply and how the patterns of Part II can resolve them.
我们正在为一家货运公司开发新软件。最初的要求是三个基本功能。
We’re developing new software for a cargo shipping company. The initial requirements are three basic functions.
1.跟踪客户货物的关键处理
1. Track key handling of customer cargo
2.提前预订货物
2. Book cargo in advance
3.当货物到达处理地点时,自动向客户发送发票
3. Send invoices to customers automatically when the cargo reaches some point in its handling
在实际项目中,需要花费一些时间和迭代才能明确此模型。本书第三部分将深入介绍发现过程。但在这里,我们将从一个具有以下特征的模型开始:以合理的形式呈现所需的概念,我们将专注于微调细节以支持设计。
In a real project, it would take some time and iteration to get to the clarity of this model. Part III of this book will go into the discovery process in depth. But here we’ll start with a model that has the needed concepts in a reasonable form, and we’ll focus on fine-tuning the details to support design.
图 7.1。表示航运领域模型的类图
Figure 7.1. A class diagram representing a model of the shipping domain
该模型组织了领域知识,并为团队提供了一种语言。我们可以做出这样的陈述:
This model organizes domain knowledge and provides a language for the team. We can make statements like this:
“多个客户参与一件货物,每个客户都扮演不同的角色。”
“Multiple Customers are involved with a Cargo, each playing a different role.”
“货物运送目标已指定。”
“The Cargo delivery goal is specified.”
“一系列满足规范的运输活动将实现运输目标。”
“A series of Carrier Movements satisfying the Specification will fulfill the delivery goal.”
模型中的每个对象都有明确的含义:
Each object in the model has a clear meaning:
处理事件是针对货物采取的独立行动,例如将其装船或清关。此类事件可能会被细化为不同类型事件的层次结构,例如装载、卸载或由接收方认领。
A Handling Event is a discrete action taken with the Cargo, such as loading it onto a ship or clearing it through customs. This class would probably be elaborated into a hierarchy of different kinds of incidents, such as loading, unloading, or being claimed by the receiver.
交货规格 定义送货目标,至少包括目的地和到达日期,但可能更复杂。此类遵循SPECIFICATION模式(参见第 9 章)。
Delivery Specification defines a delivery goal, which at minimum would include a destination and an arrival date, but it can be more complex. This class follows the SPECIFICATION pattern (see Chapter 9).
这个责任本来可以由Cargo对象承担,但是Delivery Specification的抽象至少有三个优点。
This responsibility could have been taken on by the Cargo object, but the abstraction of Delivery Specification gives at least three advantages.
1.如果没有交付规范,Cargo对象将负责指定交付目标的所有属性和关联的详细含义。这会使Cargo变得混乱,难以理解或更改。
1. Without Delivery Specification, the Cargo object would be responsible for the detailed meaning of all those attributes and associations for specifying the delivery goal. This would clutter up Cargo and make it harder to understand or change.
2.这种抽象使得在解释整个模型时隐藏细节变得简单而安全。例如,交付规范中可能还包含其他标准,但这种详细程度的图表不必公开这些标准。该图告诉读者,交付有一个规范,而其中的细节并不重要(事实上,以后可以轻松更改)。
2. This abstraction makes it easy and safe to suppress detail when explaining the model as a whole. For example, there could be other criteria encapsulated in the Delivery Specification, but a diagram at this level of detail would not have to expose it. The diagram is telling the reader that there is a SPECIFICATION of delivery, and the details of that are not important to think about (and, in fact, could be easily changed later).
3.该模型表达力更强,增加Delivery Specification明确表示货物的具体运送方式尚未确定,但必须完成Delivery Specification中规定的目标。
3. This model is more expressive. Adding Delivery Specification says explicitly that the exact means of delivery of the Cargo is undetermined, but that it must accomplish the goal set out in the Delivery Specification.
角色区分了客户在运输过程中扮演的不同角色。一个是“发货人”,一个是“收货人”,一个是“付款人”,等等。由于只有一个客户可以为特定的货物扮演给定的角色,因此关联成为合格的多对一,而不是多对多。角色可以简单地实现为一个字符串,或者如果需要其他行为,也可以实现为一个类。
A role distinguishes the different parts played by Customers in a shipment. One is the “shipper,” one the “receiver,” one the “payer,” and so on. Because only one Customer can play a given role for a particular Cargo, the association becomes a qualified many-to-one instead of many-to-many. Role might be implemented as simply a string, or it could be a class if other behavior is needed.
承运人移动表示特定承运人(如卡车或轮船)从一个地点到另一个地点的一次特定行程。货物可以通过装载到承运人上,在一次或多次承运人移动期间从一个地方运送到另一个地方。
Carrier Movement represents one particular trip by a particular Carrier (such as a truck or a ship) from one Location to another. Cargoes can ride from place to place by being loaded onto Carriers for the duration of one or more Carrier Movements.
交付历史反映了货物实际发生的情况,而交付规范则描述了目标。交付历史对象可以通过分析上次装载或卸载以及相应承运人移动的目的地来计算货物的当前位置。成功交付将以交付历史结束 满足了交付规范的目标。
Delivery History reflects what has actually happened to a Cargo, as opposed to the Delivery Specification, which describes goals. A Delivery History object can compute the current Location of the Cargo by analyzing the last load or unload and the destination of the corresponding Carrier Movement. A successful delivery would end with a Delivery History that satisfied the goals of the Delivery Specification.
满足上述要求所需的所有概念都存在于此模型中,假设有适当的机制来持久化对象、查找相关对象等。此类实施问题未在模型中处理,但必须在设计中处理。
All the concepts needed to work through the requirements just described are present in this model, assuming appropriate mechanisms to persist the objects, find the relevant objects, and so on. Such implementation issues are not dealt with in the model, but they must be in the design.
为了制定出可靠的实施方案,该模型仍需要一些澄清和完善。
In order to frame up a solid implementation, this model still needs some clarification and tightening.
请记住,通常情况下,模型改进、设计和实现应该在迭代开发过程中齐头并进。但在本章中,为了解释清楚,我们将从一个相对成熟的模型开始,并且更改将严格出于将该模型与实际实现相联系的需求,使用构建块模式。
Remember, ordinarily, model refinement, design, and implementation should go hand-in-hand in an iterative development process. But in this chapter, for clarity of explanation, we are starting with a relatively mature model, and changes will be motivated strictly by the need to connect that model with a practical implementation, employing the building block patterns.
通常,随着模型被改进以更好地支持设计,它也应该被改进以反映对该领域的新见解。但在本章中,为了解释清楚,更改将严格出于与实际实现相关的需要,使用构建块模式。
Ordinarily, as the model is being refined to support the design better, it should also be refined to reflect new insight into the domain. But in this chapter, for clarity of explanation, changes will be strictly motivated by the need to connect with a practical implementation, employing the building block patterns.
为了防止领域职责与系统其他部分的职责混淆,我们应用分层架构来划分领域层。
To prevent domain responsibilities from being mixed with those of other parts of the system, let’s apply LAYERED ARCHITECTURE to mark off a domain layer.
无需深入分析,我们就可以确定三个用户级应用程序功能,并将其分配给三个应用程序层类。
Without going into deep analysis, we can identify three user-level application functions, which we can assign to three application layer classes.
1.跟踪查询可以查看特定货物过去和现在的处理情况
1. A Tracking Query that can access past and present handling of a particular Cargo
2.预订应用程序允许注册新货物并为其准备系统
2. A Booking Application that allows a new Cargo to be registered and prepares the system for it
3.事件记录应用程序可以记录货物的每次处理(提供跟踪查询找到的信息)
3. An Incident Logging Application that can record each handling of the Cargo (providing the information that is found by the Tracking Query)
这些应用程序类是协调者。它们不应该找出它们所提出问题的答案。那是领域层的工作。
These application classes are coordinators. They should not work out the answers to the questions they ask. That is the domain layer’s job.
依次考虑每个对象,我们将寻找必须跟踪的身份或表示的基本值。首先,我们将讨论明确的情况,然后考虑比较模糊的情况。
Considering each object in turn, we’ll look for identity that must be tracked or a basic value that is represented. First we’ll go through the clear-cut cases and then consider the more ambiguous ones.
让我们从一个简单的问题开始。客户对象代表一个人或一个公司,即通常意义上的实体。客户对象显然具有对用户重要的身份,因此它是模型中的实体。如何跟踪它?税号在某些情况下可能是合适的,但跨国公司不能使用它。这个问题需要咨询领域专家。我们与航运公司的一位商人讨论了这个问题,我们发现该公司已经有一个客户数据库,其中每个客户在第一次销售联系时都会被分配一个 ID 号。这个 ID 已经在整个公司使用;在我们的软件中使用该号码将在这些系统之间建立身份的连续性。它最初将是一个手动输入。
Let’s start with an easy one. A Customer object represents a person or a company, an entity in the usual sense of the word. The Customer object clearly has identity that matters to the user, so it is an ENTITY in the model. How to track it? Tax ID might be appropriate in some cases, but an international company could not use that. This question calls for consultation with a domain expert. We discuss the problem with a businessperson in the shipping company, and we discover that the company already has a customer database in which each Customer is assigned an ID number at first sales contact. This ID is already used throughout the company; using the number in our software will establish continuity of identity between those systems. It will initially be a manual entry.
两个相同的板条箱必须是可区分的,因此Cargo对象是ENTITIES。实际上,所有运输公司都会为每件货物分配跟踪 ID。此 ID 将自动生成,用户可见,在这种情况下,可能会在预订时传达给客户。
Two identical crates must be distinguishable, so Cargo objects are ENTITIES. In practice, all shipping companies assign tracking IDs to each piece of cargo. This ID will be automatically generated, visible to the user, and in this case, probably conveyed to the customer at booking time.
我们关心这些个别事件,因为它们使我们能够跟踪正在发生的事情。它们反映了现实世界中的事件,这些事件通常不可互换,因此它们是实体。每个承运人移动都将通过从运输时间表中获得的代码来识别。
We care about such individual incidents because they allow us to keep track of what is going on. They reflect real-world events, which are not usually interchangeable, so they are ENTITIES. Each Carrier Movement will be identified by a code obtained from a shipping schedule.
与领域专家的另一次讨论表明,处理事件可以通过货物ID的组合来唯一标识,完成时间、类型等,例如同一件货物不能同时装卸。
Another discussion with a domain expert reveals that Handling Events can be uniquely identified by the combination of Cargo ID, completion time, and type. For example, the same Cargo cannot be both loaded and unloaded at the same time.
两个同名的地点并不相同。纬度和经度可以提供唯一的键,但可能不太实用,因为这些测量值对于本系统的大多数用途来说并不重要,而且它们相当复杂。更有可能的是,位置将成为某种地理模型的一部分,该模型将根据航道和其他特定领域的关注点将地点关联起来。因此,一个任意的、内部的、自动生成的标识符就足够了。
Two places with the same name are not the same. Latitude and longitude could provide a unique key, but probably not a very practical one, since those measurements are not of interest to most purposes of this system, and they would be fairly complicated. More likely, the Location will be part of a geographical model of some kind that will relate places according to shipping lanes and other domain-specific concerns. So an arbitrary, internal, automatically generated identifier will suffice.
这是个棘手的问题。送货历史不可互换,因此它们是实体。但送货历史与其货物具有一对一的关系,因此它实际上没有自己的身份。其身份是从拥有它的货物那里借来的。当我们对聚合进行建模时,这一点会变得更加清晰。
This is a tricky one. Delivery Histories are not interchangeable, so they are ENTITIES. But a Delivery History has a one-to-one relationship with its Cargo, so it doesn’t really have an identity of its own. Its identity is borrowed from the Cargo that owns it. This will become clearer when we model the AGGREGATES.
尽管它代表了Cargo的目标,但这种抽象并不依赖于Cargo。它实际上表达了某些Delivery History的假设状态。我们希望附加到Cargo上的Delivery History最终能够满足附加到Cargo上的Delivery Specification。如果我们有两个Cargo发往同一个地方,它们可以共享相同的Delivery Specification,但它们不能共享相同的Delivery History,即使历史记录开始时相同(为空)。Delivery Specification是VALUE OBJECTS。
Although it represents the goal of a Cargo, this abstraction does not depend on Cargo. It really expresses a hypothetical state of some Delivery History. We hope that the Delivery History attached to our Cargo will eventually satisfy the Delivery Specification attached to our Cargo. If we had two Cargoes going to the same place, they could share the same Delivery Specification, but they could not share the same Delivery History, even though the histories start out the same (empty). Delivery Specifications are VALUE OBJECTS.
角色说明了它所符合的关联,但没有历史或连续性。它是一个VALUE OBJECT,可以在不同的货物/客户关联之间共享。
Role says something about the association it qualifies, but it has no history or continuity. It is a VALUE OBJECT, and it could be shared among different Cargo/Customer associations.
其他属性(如时间戳或名称)是值对象。
Other attributes such as time stamps or names are VALUE OBJECTS.
原始图表中的关联均未指定遍历方向,但双向关联在设计中存在问题。此外,遍历方向通常可以洞察领域,从而深化模型本身。
None of the associations in the original diagram specified a traversal direction, but bidirectional associations are problematic in a design. Also, traversal direction often captures insight into the domain, deepening the model itself.
如果Customer直接引用其运送的每件Cargo,那么对于长期回头客Customers来说,这将变得很麻烦。此外, Customer的概念并不特定于Cargo。在大型系统中,Customer可能要扮演许多角色,与许多对象打交道。最好让它摆脱这些特定的责任。如果我们需要按Customer查找Cargo ,可以通过数据库查询来完成。我们将在本章后面的REPOSITORIES部分中回到这个问题。
If the Customer has a direct reference to every Cargo it has shipped, it will become cumbersome for long-term, repeat Customers. Also, the concept of a Customer is not specific to Cargo. In a large system, the Customer may have roles to play with many objects. Best to keep it free of such specific responsibilities. If we need the ability to find Cargoes by Customer, this can be done through a database query. We’ll return to this issue later in this chapter, in the section on REPOSITORIES.
如果我们的应用程序正在跟踪船舶库存,那么从承运人流动到处理事件的遍历将很重要。但我们的业务只需要跟踪货物。使关联仅可从处理事件到承运人流动进行遍历可以体现对我们业务的理解。这还将实现简化为简单的对象引用,因为不允许具有多重性的方向。
If our application were tracking the inventory of ships, traversal from Carrier Movement to Handling Event would be important. But our business needs to track only the Cargo. Making the association traversable only from Handling Event to Carrier Movement captures that understanding of our business. This also reduces the implementation to a simple object reference, because the direction with multiplicity was disallowed.
下一页的图 7.2解释了其余决策背后的理由。
The rationale behind the remaining decisions is explained in Figure 7.2, on the next page.
图 7.2. 某些关联中的遍历方向受到限制。
Figure 7.2. Traversal direction has been constrained on some associations.
我们的模型中有一个循环引用:Cargo知道它的Delivery History,其中包含一系列Handling Events,而这些事件又指向Cargo。循环引用在逻辑上存在于许多领域中,有时在设计中也是必要的,但它们很难维护。实现选择可以通过避免将相同的信息保存在必须保持同步的两个地方来提供帮助。在这种情况下,我们可以在初始原型中创建一个简单但脆弱的实现(用 Java),方法是为Delivery History提供一个包含Handling Events的List对象。但在某些时候,我们可能希望放弃该集合,转而使用以Cargo为关键字的数据库查找。在选择REPOSITORIES时将再次讨论此讨论。如果查看历史记录的查询相对不频繁,这应该会提供良好的性能,简化维护,并减少添加Handling Events的开销。如果此查询非常频繁,那么最好继续维护直接指针。这些设计权衡了实现的简单性和性能。模型是相同的;它包含循环和双向关联。
There is one circular reference in our model: Cargo knows its Delivery History, which holds a series of Handling Events, which in turn point back to the Cargo. Circular references logically exist in many domains and are sometimes necessary in design as well, but they are tricky to maintain. Implementation choices can help by avoiding holding the same information in two places that must be kept synchronized. In this case, we can make a simple but fragile implementation (in Java) in an initial prototype, by giving Delivery History a List object containing Handling Events. But at some point we’ll probably want to drop the collection in favor of a database lookup with Cargo as the key. This discussion will be taken up again when choosing REPOSITORIES. If the query to see the history is relatively infrequent, this should give good performance, simplify maintenance, and reduce the overhead of adding Handling Events. If this query is very frequent, then it is better to go ahead and maintain the direct pointer. These design trade-offs balance simplicity of implementation against performance. The model is the same; it contains the cycle and the bidirectional association.
客户、位置和承运人流动都有自己的身份,并由许多货物共享,因此它们必须是自己的聚合的根,其中包含它们的属性以及可能在本讨论的详细程度以下的其他对象。货物也是一个明显的聚合根,但在哪里划定边界需要一些思考。
Customer, Location, and Carrier Movement have their own identities and are shared by many Cargoes, so they must be the roots of their own AGGREGATES, which contain their attributes and possibly other objects below the level of detail of this discussion. Cargo is also an obvious AGGREGATE root, but where to draw the boundary takes some thought.
货物聚合 可以囊括除特定货物之外不存在的所有内容,包括交付历史、交付规范和处理事件。这适用于交付历史。没有人会直接查找交付历史而不想要货物本身。无需直接全局访问,并且其身份实际上只是从Cargo 派生而来,因此Delivery History非常适合Cargo 的边界,并且它不需要是根。Delivery Specification是一个VALUE OBJECT ,因此将其包含在Cargo AGGREGATE中没有任何复杂性。
The Cargo AGGREGATE could sweep in everything that would not exist but for the particular Cargo, which would include the Delivery History, the Delivery Specification, and the Handling Events. This fits for Delivery History. No one would look up a Delivery History directly without wanting the Cargo itself. With no need for direct global access, and with an identity that is really just derived from the Cargo, the Delivery History fits nicely inside Cargo’s boundary, and it does not need to be a root. The Delivery Specification is a VALUE OBJECT, so there are no complications from including it in the Cargo AGGREGATE.
处理事件是另一回事。之前,我们考虑了两种可能的数据库查询来搜索这些:一种是查找交付历史记录的处理事件作为集合的可能替代方案,该查询将在货物聚合中本地化;另一种将用于查找要加载和准备特定承运人移动的所有操作。在第二种情况下,即使与货物本身分开考虑,处理货物的活动似乎也具有一定意义。因此,处理事件应该是其自身聚合的根源。
The Handling Event is another matter. Previously we have considered two possible database queries that would search for these: one, to find the Handling Events for a Delivery History as a possible alternative to the collection, would be local within the Cargo AGGREGATE; the other would be used to find all the operations to load and prepare for a particular Carrier Movement. In the second case, it seems that the activity of handling the Cargo has some meaning even when considered apart from the Cargo itself. So the Handling Event should be the root of its own AGGREGATE.
图 7.3。对模型施加的GGREGATE边界。(注意:绘制边界之外的ENTITY暗示为其自身AGGREGATE的根。)
Figure 7.3. AGGREGATE boundaries imposed on the model. (Note: An ENTITY outside a drawn boundary is implied to be the root of its own AGGREGATE.)
有五个实体 在设计中,它们是AGGREGATES的根,因此我们可以将考虑范围限制在这些对象上,因为其他任何对象都不允许有REPOSITORIES。
There are five ENTITIES in the design that are roots of AGGREGATES, so we can limit our consideration to these, since none of the other objects is allowed to have REPOSITORIES.
要决定这些候选者中的哪一个应该拥有REPOSITORY,我们必须回到应用程序需求。为了通过Booking Application进行预订,用户需要选择扮演各种角色(托运人、收货人等)的客户。所以我们需要一个Customer Repository。我们还需要找到一个Location来指定为Cargo的目的地,所以我们创建了一个Location Repository。
To decide which of these candidates should actually have a REPOSITORY, we must go back to the application requirements. In order to take a booking through the Booking Application, the user needs to select the Customer(s) playing the various roles (shipper, receiver, and so on). So we need a Customer Repository. We also need to find a Location to specify as the destination for the Cargo, so we create a Location Repository.
活动日志应用程序需要允许用户查找正在装载货物的承运人移动情况,因此我们需要一个承运人移动存储库。该用户还必须告诉系统已装载了哪些货物,因此我们需要一个货物存储库。
The Activity Logging Application needs to allow the user to look up the Carrier Movement that a Cargo is being loaded onto, so we need a Carrier Movement Repository. This user must also tell the system which Cargo has been loaded, so we need a Cargo Repository.
图 7.4. R EPOSITORIES允许访问选定的AGGREGATE根。
Figure 7.4. REPOSITORIES give access to selected AGGREGATE roots.
目前还没有处理事件存储库,因为我们决定在第一次迭代中将与交付历史的关联实现为一个集合,并且我们没有应用程序要求找出已加载到承运人移动中的内容。这两个原因都可能发生变化;如果它们发生了变化,那么我们将添加一个存储库。
For now there is no Handling Event Repository, because we decided to implement the association with Delivery History as a collection in the first iteration, and we have no application requirement to find out what has been loaded onto a Carrier Movement. Either of these reasons could change; if they did, then we would add a REPOSITORY.
为了检验所有这些决定,我们必须不断地逐步验证各种场景,以确认我们能够有效地解决应用问题。
To cross-check all these decisions, we have to constantly step through scenarios to confirm that we can solve application problems effectively.
偶尔会有客户打电话说:“哦,不!我们说要把货物送到哈肯萨克,但我们确实需要它到霍博肯。”我们来这里是为了服务,所以系统需要为这种变化做好准备。
Occasionally a Customer calls up and says, “Oh no! We said to send our cargo to Hackensack, but we really need it in Hoboken.” We are here to serve, so the system is required to provide for this change.
交付规范是一个VALUE OBJECT,因此最简单的方法是将其扔掉并得到一个新的,然后使用Cargo上的 setter 方法将旧的替换为新的。
Delivery Specification is a VALUE OBJECT, so it would be simplest to just to throw it away and get a new one, then use a setter method on Cargo to replace the old one with the new one.
用户表示,来自同一客户的重复预订往往相似,因此他们希望使用旧货物作为新货物的原型。该应用程序将允许他们在REPOSITORY中找到货物,然后选择一个命令以根据所选货物创建新货物。我们将使用PROTOTYPE模式(Gamma 等人,1995 年)进行设计。
The users say that repeated bookings from the same Customers tend to be similar, so they want to use old Cargoes as prototypes for new ones. The application will allow them to find a Cargo in the REPOSITORY and then select a command to create a new Cargo based on the selected one. We’ll design this using the PROTOTYPE pattern (Gamma et al. 1995).
Cargo是一个ENTITY ,也是AGGREGATE的根。因此,必须小心地复制它;我们需要考虑其AGGREGATE边界所包围的每个对象或属性应该发生什么。让我们逐一介绍一下:
Cargo is an ENTITY and is the root of an AGGREGATE. Therefore, it must be copied carefully; we need to consider what should happen to each object or attribute enclosed by its AGGREGATE boundary. Let’s go over each one:
•交付历史记录:我们应该创建一个新的空历史记录,因为旧历史记录不适用。这是AGGREGATE边界内的ENTITIES的常见情况。
• Delivery History: We should create a new, empty one, because the history of the old one doesn’t apply. This is the usual case with ENTITIES inside the AGGREGATE boundary.
•客户角色:我们应该复制包含对客户的键引用的Map (或其他集合) ,包括键,因为它们很可能在新货件中扮演相同的角色。但我们必须小心,不要复制客户 对象本身。我们最终必须引用与引用的旧Cargo对象相同的Customer对象,因为它们是聚合边界之外的实体。
• Customer Roles: We should copy the Map (or other collection) that holds the keyed references to Customers, including the keys, because they are likely to play the same roles in the new shipment. But we have to be careful not to copy the Customer objects themselves. We must end up with references to the same Customer objects as the old Cargo object referenced, because they are ENTITIES outside the AGGREGATE boundary.
•跟踪 ID:我们必须提供一个来自与从头创建新货物时相同来源的新跟踪 ID 。
• Tracking ID: We must provide a new Tracking ID from the same source as we would when creating a new Cargo from scratch.
请注意,我们已经复制了Cargo AGGREGATE边界内的所有内容,我们对副本做了一些修改,但我们根本没有影响AGGREGATE边界之外的任何内容。
Notice that we have copied everything inside the Cargo AGGREGATE boundary, we have made some modifications to the copy, but we have affected nothing outside the AGGREGATE boundary at all.
即使我们为Cargo建立了一个精美的FACTORY,或者像在“重复业务”场景中一样使用另一个Cargo作为FACTORY,我们仍然必须有一个原始构造函数。我们希望构造函数生成一个满足其不变量的对象,或者至少在 ENTITY 的情况下,保持其身份完整。
Even if we have a fancy FACTORY for Cargo, or use another Cargo as the FACTORY, as in the “Repeat Business” scenario, we still have to have a primitive constructor. We would like the constructor to produce an object that fulfills its invariants or at least, in the case of an ENTITY, has its identity intact.
根据这些决定,我们可能会在Cargo上创建一个FACTORY方法,如下所示:
Given these decisions, we might create a FACTORY method on Cargo such as this:
公共货物副本原型(字符串新跟踪ID)
public Cargo copyPrototype(String newTrackingID)
或者我们可以在独立的FACTORY上创建一个方法,如下:
Or we might make a method on a standalone FACTORY such as this:
公共货物新货物(货物原型,字符串新TrackingID)
public Cargo newCargo(Cargo prototype, String newTrackingID)
独立的FACTORY还可以封装为新的Cargo获取新 ID(自动生成)的过程,在这种情况下它只需要一个参数:
A standalone FACTORY could also encapsulate the process of obtaining a new (automatically generated) ID for a new Cargo, in which case it would need only one argument:
公共货物新货物(货物原型)
public Cargo newCargo(Cargo prototype)
从任何一个工厂返回的结果都是相同的:货物的交货历史记录为空,交货规范为空。
The result returned from any of these FACTORIES would be the same: a Cargo with an empty Delivery History, and a null Delivery Specification.
Cargo和Delivery History之间的双向关联意味着,如果不指向其对应方,Cargo和Delivery History都是不完整的,因此必须一起创建它们。请记住, Cargo是包含Delivery History的AGGREGATE的根。因此,我们可以允许Cargo 的构造函数或FACTORY创建Delivery History。Delivery History构造函数将以Cargo作为参数。结果将如下所示:
The two-way association between Cargo and Delivery History means that neither Cargo nor Delivery History is complete without pointing to its counterpart, so they must be created together. Remember that Cargo is the root of the AGGREGATE that includes Delivery History. Therefore, we can allow Cargo’s constructor or FACTORY to create a Delivery History. The Delivery History constructor will take a Cargo as an argument. The result would be something like this:
公共货物(String id){
trackingID = id;
deliveryHistory = new DeliveryHistory(this);
customerRoles = new HashMap();
}
public Cargo(String id) {
trackingID = id;
deliveryHistory = new DeliveryHistory(this);
customerRoles = new HashMap();
}
结果是一个新的Cargo,带有一个新的Delivery History,该历史指向Cargo。Delivery History构造函数由其AGGREGATE根(即Cargo )专门使用,以便封装Cargo的组成。
The result is a new Cargo with a new Delivery History that points back to the Cargo. The Delivery History constructor is used exclusively by its AGGREGATE root, namely Cargo, so that the composition of Cargo is encapsulated.
每次在现实世界中处理货物时,某些用户都会使用事件日志应用程序输入处理事件。
Each time the cargo is handled in the real world, some user will enter a Handling Event using the Incident Logging Application.
每个类都必须有原始构造函数。由于处理事件是一个ENTITY,因此必须将定义其身份的所有属性传递给构造函数。如前所述,处理事件由其货物ID、完成时间和事件类型的组合唯一标识。处理事件的唯一其他属性是与承运人移动的关联,某些类型的处理事件甚至没有该属性。创建有效处理事件的基本构造函数将是:
Every class must have primitive constructors. Because the Handling Event is an ENTITY, all attributes that define its identity must be passed to the constructor. As discussed previously, the Handling Event is uniquely identified by the combination of the ID of its Cargo, the completion time, and the event type. The only other attribute of Handling Event is the association to a Carrier Movement, which some types of Handling Events don’t even have. A basic constructor that creates a valid Handling Event would be:
公共 HandlingEvent(Cargo c,String eventType,Date timeStamp){
handled = c;
type = eventType;
completionTime = timeStamp;
}
public HandlingEvent(Cargo c, String eventType, Date timeStamp) {
handled = c;
type = eventType;
completionTime = timeStamp;
}
ENTITY的非识别属性通常可以稍后添加。在这种情况下,处理事件的所有属性都将在初始事务中设置,并且永远不会更改(除非可能为了纠正数据输入错误),因此,为每种事件类型的处理事件添加一个简单的工厂方法并采用所有必要的参数可能会很方便,并使客户端代码更具表现力。例如, “加载事件”确实涉及承运人移动:
Nonidentifying attributes of an ENTITY can usually be added later. In this case, all attributes of the Handling Event are going to be set in the initial transaction and never altered (except possibly for correcting a data-entry error), so it could be convenient, and make client code more expressive, to add a simple FACTORY METHOD to Handling Event for each event type, taking all the necessary arguments. For example, a “loading event” does involve a Carrier Movement:
公共静态 HandlingEvent newLoading(
Cargo c,CarrierMovement loadedOnto,Date timeStamp){
HandlingEvent result =
new HandlingEvent(c,LOADING_EVENT,timeStamp);
result.setCarrierMovement(loadedOnto);
返回结果;
}
public static HandlingEvent newLoading(
Cargo c, CarrierMovement loadedOnto, Date timeStamp) {
HandlingEvent result =
new HandlingEvent(c, LOADING_EVENT, timeStamp);
result.setCarrierMovement(loadedOnto);
return result;
}
处理事件 模型中的抽象可能封装了各种专门的处理事件类,从装载和卸载到密封、存储和其他与Carriers无关的活动。它们可能被实现为多个子类,或者具有复杂的初始化 - 或者两者兼而有之。通过向每种类型的基类(处理事件)添加FACTORY METHODS,实例创建被抽象化,使客户端无需了解实现。FACTORY负责了解要实例化哪个类以及如何初始化它。
The Handling Event in the model is an abstraction that might encapsulate a variety of specialized Handling Event classes, ranging from loading and unloading to sealing, storing, and other activities not related to Carriers. They might be implemented as multiple subclasses or have complicated initialization—or both. By adding FACTORY METHODS to the base class (Handling Event) for each type, instance creation is abstracted, freeing the client from knowledge of the implementation. The FACTORY is responsible for knowing what class was to be instantiated and how it should be initialized.
不幸的是,事情并没有那么简单。从Cargo到Delivery History再到History Event再回到Cargo 的引用循环使实例创建变得复杂。Delivery History包含与其Cargo相关的Handling Events集合,新对象必须作为事务的一部分添加到此集合中。如果没有创建这个反向指针,对象就会不一致。
Unfortunately, the story isn’t quite that simple. The cycle of references, from Cargo to Delivery History to History Event and back to Cargo, complicates instance creation. The Delivery History holds a collection of Handling Events relevant to its Cargo, and the new object must be added to this collection as part of the transaction. If this back-pointer were not created, the objects would be inconsistent.
图 7.5. 添加处理事件需要将其插入到交付历史记录中。
Figure 7.5. Adding a Handling Event requires inserting it into a Delivery History.
反向指针的创建可以封装在FACTORY中(并保存在它所属的领域层中),但现在我们将研究一种可以完全消除这种尴尬交互的替代设计。
Creation of the back-pointer could be encapsulated in the FACTORY (and kept in the domain layer where it belongs), but now we’ll look at an alternative design that eliminates this awkward interaction altogether.
建模和设计并不是一个持续前进的过程。除非经常进行重构,利用新见解改进模型和设计,否则整个过程就会停滞不前。
Modeling and design is not a constant forward process. It will grind to a halt unless there is frequent refactoring to take advantage of new insights to improve the model and the design.
到目前为止,这个设计有几个麻烦的方面,尽管它确实有效并且确实反映了模型。在开始设计时似乎不重要的问题开始变得令人烦恼。让我们回过头来回顾其中一个问题,并在事后回顾的基础上,将设计成果堆放在对我们有利的位置。
By now, there are a couple of cumbersome aspects to this design, although it does work and it does reflect the model. Problems that didn’t seem important when starting the design are beginning to be annoying. Let’s go back to one of them and, with the benefit of hindsight, stack the design deck in our favor.
添加处理事件时需要更新交付历史记录,这会导致货物聚合参与交易。如果其他用户同时修改货物,处理事件交易可能会失败或延迟。输入处理事件是一项需要快速简单的操作活动,因此一个重要的应用程序要求是能够无争用地输入处理事件。这促使我们考虑不同的设计。
The need to update Delivery History when adding a Handling Event gets the Cargo AGGREGATE involved in the transaction. If some other user was modifying Cargo at the same time, the Handling Event transaction could fail or be delayed. Entering a Handling Event is an operational activity that needs to be quick and simple, so an important application requirement is the ability to enter Handling Events without contention. This pushes us to consider a different design.
用查询替换Delivery History 的处理事件集合将允许添加处理事件,而不会在其自身的AGGREGATE之外引发任何完整性问题。此更改将使此类交易能够不受干扰地完成。如果输入了大量的处理事件,而查询相对较少,则此设计效率更高。事实上,如果关系数据库是底层技术,那么无论如何,查询可能都在幕后使用以模拟集合。使用查询而不是集合也会降低维护Cargo和Handling Event之间循环引用一致性的难度。
Replacing the Delivery History’s collection of Handling Events with a query would allow Handling Events to be added without raising any integrity issues outside its own AGGREGATE. This change would enable such transactions to complete without interference. If there are a lot of Handling Events being entered and relatively few queries, this design is more efficient. In fact, if a relational database is the underlying technology, a query was probably being used under the covers anyway to emulate the collection. Using a query rather than a collection would also reduce the difficulty of maintaining consistency in the cyclical reference between Cargo and Handling Event.
为了负责查询,我们将添加一个用于处理事件的存储库。处理事件存储库将支持查询与某个货物相关的事件。此外,存储库可以提供优化的查询,以有效地回答特定问题。例如,如果频繁访问的路径是交付历史记录 查找最后报告的装载或卸载,以推断货物的当前状态,可以设计一个查询来返回相关的处理事件。如果我们想要一个查询来查找特定承运人移动中装载的所有货物,我们可以轻松添加它。
To take responsibility for the queries, we’ll add a REPOSITORY for Handling Events. The Handling Event Repository will support a query for the Events related to a certain Cargo. In addition, the REPOSITORY can provide queries optimized to answer specific questions efficiently. For example, if a frequent access path is the Delivery History finding the last reported load or unload, in order to infer the current status of the Cargo, a query could be devised to return just that relevant Handling Event. And if we wanted a query to find all Cargoes loaded on a particular Carrier Movement, we could easily add it.
图 7.6. 将Delivery History 的处理事件集合实现为查询,使得处理事件的插入变得简单,并且不会与Cargo AGGREGATE发生争用。
Figure 7.6. Implementing Delivery History’s collection of Handling Events as a query makes insertion of Handling Events simple and free of contention with the Cargo AGGREGATE.
这样一来, Delivery History就不再具有持久状态。此时,实际上没有必要保留它。我们可以在需要回答某个问题时派生Delivery History本身。我们可以派生此对象,因为尽管ENTITY将被反复重新创建,但与同一Cargo对象的关联会保持化身之间的连续性。
This leaves the Delivery History with no persistent state. At this point, there is no real need to keep it around. We could derive Delivery History itself whenever it is needed to answer some question. We can derive this object because, although the ENTITY will be repeatedly recreated, the association with the same Cargo object maintains the thread of continuity between incarnations.
循环引用的创建和维护不再棘手。Cargo Factory将被简化,不再将空的Delivery History附加到新实例。数据库空间可以略微减少,并且持久对象的实际数量可能会减少在某些对象数据库中,这是一种有限的资源。如果常见的使用模式是用户在货物到达之前很少查询货物的状态,那么就可以完全避免大量不必要的工作。
The circular reference is no longer tricky to create and maintain. The Cargo Factory will be simplified to no longer attach an empty Delivery History to new instances. Database space can be reduced slightly, and the actual number of persistent objects might be reduced considerably, which is a limited resource in some object databases. If the common usage pattern is that the user seldom queries for the status of a Cargo until it arrives, then a lot of unneeded work will be avoided altogether.
另一方面,如果我们使用对象数据库,遍历关联或显式集合可能比REPOSITORY查询快得多。如果访问模式包括频繁列出完整历史记录,而不是偶尔针对最后一个位置进行查询,则性能权衡可能有利于显式集合。请记住,添加的功能(“这个Carrier Movement上有什么?”)尚未被请求,也可能永远不会被请求,所以我们不想为该选项付出太多代价。
On the other hand, if we are using an object database, traversing an association or an explicit collection is probably much faster than a REPOSITORY query. If the access pattern includes frequent listing of the full history, rather than the occasional targeted query of last position, the performance trade-off might favor the explicit collection. And remember that the added feature (“What is on this Carrier Movement?”) hasn’t been requested yet, and may never be, so we don’t want to pay much for that option.
此类替代方案和设计权衡无处不在,仅就这个简化的小系统而言,我就能想出很多例子。但重点是,这些都是同一模型中的自由度。通过对VALUES、ENTITIES及其AGGREGATES进行建模,我们减少了此类设计变更的影响。例如,在这种情况下,所有变更都封装在Cargo 的 AGGREGATE边界内。它还需要添加Handling Event Repository,但不需要对Handling Event本身进行任何重新设计(尽管可能涉及一些实施变更,具体取决于REPOSITORY框架的细节)。
These kinds of alternatives and design trade-offs are everywhere, and I could come up with lots of examples just in this little simplified system. But the important point is that these are degrees of freedom within the same model. By modeling VALUES, ENTITIES, and their AGGREGATES as we have, we have reduced the impact of such design changes. For example, in this case all changes are encapsulated within the Cargo’s AGGREGATE boundary. It also required the addition of the Handling Event Repository, but it did not call for any redesign of the Handling Event itself (although some implementation changes might be involved, depending on the details of the REPOSITORY framework).
到目前为止,我们研究的对象很少,因此模块化不是问题。现在让我们看一下运输模型的稍大一点的部分(当然,仍然简化了),看看它如何组织成会影响模型的模块。
So far we’ve been looking at so few objects that modularity is not an issue. Now let’s look at a little bigger part of a shipping model (though still simplified, of course) to see its organization into MODULES that will affect the model.
图 7.7显示了本书的假设热心读者整齐划分的模型。该图是第 5 章中提出的基础结构驱动打包问题的变体。在这种情况下,对象已根据各自遵循的模式进行分组。结果是概念上关系不大(内聚性低)的对象挤在一起,并且所有模块之间的关联杂乱无章(耦合性高)。这些包讲述了一个故事,但这不是运输的故事;而是开发人员当时正在阅读的故事。
Figure 7.7 shows a model neatly partitioned by a hypothetical enthusiastic reader of this book. This diagram is a variation on the infrastructure-driven packaging problem raised in Chapter 5. In this case, the objects have been grouped according to the pattern each follows. The result is that objects that conceptually have little relationship (low cohesion) are crammed together, and associations run willy-nilly between all the MODULES (high coupling). The packages tell a story, but it is not the story of shipping; it is the story of what the developer was reading at the time.
图 7.7。这些模块不传达领域知识。
Figure 7.7. These MODULES do not convey domain knowledge.
按模式分区似乎是一个明显的错误,但实际上它并不比将持久对象与瞬态对象或任何其他不以对象含义为基础的方法方案分离更不合理。
Partitioning by pattern may seem like an obvious error, but it is not really any less sensible than separating persistent objects from transient ones or any other methodical scheme that is not grounded in the meaning of the objects.
相反,我们应该寻找有凝聚力的概念,并专注于我们想要与项目中的其他人沟通的内容。与小规模建模决策一样,有很多方法可以做到这一点。图 7.8显示了一个简单的方法。
Instead, we should be looking for the cohesive concepts and focusing on what we want to communicate to others on the project. As with smaller scale modeling decisions, there are many ways to do it. Figure 7.8 shows a straightforward one.
图 7.8.基于广泛领域概念的模块
Figure 7.8. MODULES based on broad domain concepts
图 7.8中的模块名称有助于团队的语言。我们公司为客户运输货物,以便我们向他们开具账单。我们的销售和营销人员与客户打交道,并与他们达成协议。运营人员负责运输,将货物运送到指定目的地。后台办公室负责开具账单,根据客户协议中的定价提交发票。这是我可以用这组模块讲述的一个故事。
The MODULE names in Figure 7.8 contribute to the team’s language. Our company does shipping for customers so that we can bill them. Our sales and marketing people deal with customers, and make agreements with them. The operations people do the shipping, getting the cargo to its specified destination. The back office takes care of billing, submitting invoices according to the pricing in the customer’s agreement. That’s one story I can tell with this set of MODULES.
当然,这种直观的分解可以在连续的迭代中得到改进,甚至完全被取代,但它现在正在协助模型驱动设计,并为无处不在的语言做出贡献。
This intuitive breakdown could be refined, certainly, in successive iterations, or even replaced entirely, but it is now aiding MODEL-DRIVEN DESIGN and contributing to the UBIQUITOUS LANGUAGE.
到目前为止,我们一直在努力实现初始需求和模型。现在,我们将添加第一批主要新功能。
Up to this point, we’ve been working off the initial requirements and model. Now the first major new functions are going to be added.
虚拟航运公司的销售部门使用其他软件来管理客户关系、销售预测等第四。一项功能支持收益管理,允许公司根据货物类型、出发地和目的地或他们可能选择的可以作为类别名称输入的任何其他因素来分配他们将尝试预订的特定类型的货物数量。这些构成了每种类型的销售目标,以便利润更高的业务类型不会被利润较低的货物挤占,同时避免预订不足(未充分利用其运输能力)或过度预订(导致频繁地取消货物,从而损害客户关系)。
The sales division of the imaginary shipping company uses other software to manage client relationships, sales projections, and so forth. One feature supports yield management by allowing the firm to allocate how much cargo of specific types they will attempt to book based on the type of goods, the origin and destination, or any other factor they may choose that can be entered as a category name. These constitute goals of how much will be sold of each type, so that more profitable types of business will not be crowded out by less profitable cargoes, while at the same time avoiding underbooking (not fully utilizing their shipping capacity) or excessive overbooking (resulting in bumping cargo so often that it hurts customer relationships).
现在他们希望将此功能与预订系统集成。当预订进入时,他们希望根据这些分配进行检查,以确定是否应该接受。
Now they want this feature to be integrated with the booking system. When a booking comes in, they want it checked against these allocations to see if it should be accepted.
所需信息位于两个位置,预订应用程序必须查询这些信息,以便接受或拒绝请求的预订。一般信息流的示意图如下所示。
The information needed resides in two places, which will have to be queried by the Booking Application so that it can either accept or reject the requested booking. A sketch of the general information flows looks something like this.
图 7.9。我们的预订应用程序必须使用来自销售管理系统和我们自己的域REPOSITORIES的信息。
Figure 7.9. Our Booking Application must use information from the Sales Management System and from our own domain REPOSITORIES.
销售管理系统在编写时考虑的模型与我们在此处使用的模型不同。如果预订应用程序直接与其交互,我们的应用程序将不得不适应其他系统的设计,这将使我们更难保持清晰的模型驱动设计,并会混淆通用语言。相反,让我们创建另一个类,其工作是在我们的模型和销售管理系统的语言之间进行转换。它不会是一种通用的翻译机制。它将仅公开我们的应用程序所需的功能,并根据我们的领域模型重新抽象它们。此类将充当反腐败层(第 14 章讨论)。
The Sales Management System was not written with the same model in mind that we are working with here. If the Booking Application interacts with it directly, our application will have to accommodate the other system’s design, which will make it harder to keep a clear MODEL-DRIVEN DESIGN and will confuse the UBIQUITOUS LANGUAGE. Instead, let’s create another class whose job it will be to translate between our model and the language of the Sales Management System. It will not be a general translation mechanism. It will expose just the features our application needs, and it will reabstract them in terms of our domain model. This class will act as an ANTICORRUPTION LAYER (discussed in Chapter 14).
这是销售管理系统的一个接口,因此我们首先可能会想到将其命名为“销售管理接口”。但这样一来,我们就错失了使用语言重新定义问题的机会,而这个问题对我们更有帮助。相反,让我们为需要从其他系统获取的每个分配功能定义一个服务。我们将使用一个类来实现服务,该类的名称反映了它在我们的系统中的职责:“分配检查器”。
This is an interface to the Sales Management System, so we might first think of calling it something like “Sales Management Interface.” But we would be missing an opportunity to use language to recast the problem along lines more useful to us. Instead, let’s define a SERVICE for each of the allocation functions we need to get from the other system. We’ll implement the SERVICES with a class whose name reflects its responsibility in our system: “Allocation Checker.”
如果需要其他集成(例如,使用销售管理系统的客户数据库而不是我们自己的客户 存储库),则可以创建另一个转换器,并使用服务来履行该职责。拥有一个较低级别的类(如销售管理系统接口)来处理与其他程序对话的机制可能仍然有用,但它不负责翻译。此外,它将隐藏在分配检查器后面,因此它不会出现在域设计中。
If some other integration is needed (for example, using the Sales Management System’s customer database instead of our own Customer REPOSITORY), another translator can be created with SERVICES fulfilling that responsibility. It might still be useful to have a lower level class like Sales Management System Interface to handle the machinery of talking to the other program, but it wouldn’t be responsible for translation. Also, it would be hidden behind the Allocation Checker, so it wouldn’t show up in the domain design.
现在我们已经概述了两个系统的交互,我们将提供什么样的接口来回答“可以预订多少这种类型的货物?”这个问题。棘手的问题在于定义货物的“类型” ,因为我们的领域模型尚未对货物进行分类。在销售管理系统中,货物类型只是一组类别关键字,我们可以使我们的类型符合该列表。我们可以传入一个字符串集合作为参数。但我们将错过另一个机会:这次,重新抽象另一个系统的领域。我们需要丰富我们的领域模型以适应货物有类别的知识。我们应该与领域专家集思广益,制定新概念。
Now that we have outlined the interaction of the two systems, what kind of interface are we going to supply that can answer the question “How much of this type of Cargo may be booked?” The tricky issue is to define what the “type” of a Cargo is, because our domain model does not categorize Cargoes yet. In the Sales Management System, Cargo types are just a set of category keywords, and we could conform our types to that list. We could pass in a collection of strings as an argument. But we would be passing up another opportunity: this time, to reabstract the domain of the other system. We need to enrich our domain model to accommodate the knowledge that there are categories of cargo. We should brainstorm with a domain expert to work out the new concept.
有时(如第 11 章所述),分析模式可以为我们提供建模解决方案的思路。《分析模式》(Fowler 1996)一书描述了一种解决此类问题的模式:企业细分市场。企业细分市场是一组定义业务细分方式的维度。这些维度可能包括所有已提到的航运业务维度,以及时间维度,如当月至今。在我们的分配模型中使用这个概念可以使模型更具表现力,并简化接口。一个名为“企业细分市场”的类 将出现在我们的领域模型和设计中作为一个额外的VALUE OBJECT,必须为每个Cargo派生出它。
Sometimes (as will be discussed in Chapter 11) an analysis pattern can give us an idea for a modeling solution. The book Analysis Patterns (Fowler 1996) describes a pattern that addresses this kind of problem: the ENTERPRISE SEGMENT. An ENTERPRISE SEGMENT is a set of dimensions that define a way of breaking down a business. These dimensions could include all those mentioned already for the shipping business, as well as time dimensions, such as month to date. Using this concept in our model of allocation makes the model more expressive and simplifies the interfaces. A class called “Enterprise Segment” will appear in our domain model and design as an additional VALUE OBJECT, which will have to be derived for each Cargo.
图 7.10。分配检查器充当反腐败层,根据我们的领域模型向销售管理系统提供选择性接口。
Figure 7.10. The Allocation Checker acts as an ANTICORRUPTION LAYER presenting a selective interface to the Sales Management System in terms of our domain model.
分配检查器将在企业段和外部系统的类别名称之间进行转换。 Cargo 存储库还必须提供基于企业段的查询。 在这两种情况下,都可以使用与企业段对象的协作来执行操作,而不会破坏段的封装并使其自身的实现复杂化。 (请注意,Cargo 存储库使用计数而不是实例集合来回答查询。)
The Allocation Checker will translate between Enterprise Segments and the category names of the external system. The Cargo Repository must also provide a query based on the Enterprise Segment. In both cases, collaboration with the Enterprise Segment object can be used to perform the operations without breaching the Segment’s encapsulation and complicating their own implementations. (Notice that the Cargo Repository is answering a query with a count, rather than a collection of instances.)
这个设计仍然存在一些问题。
There are still a few problems with this design.
1.我们已将应用此规则的任务交给预订应用程序:“如果分配给企业段的空间大于已预订的数量与新货物的大小之和,则接受货物。” 执行业务规则是领域责任,不应在应用程序层执行。
1. We have given the Booking Application the job of applying this rule: “A Cargo is accepted if the space allocated for its Enterprise Segment is greater than the quantity already booked plus the size of the new Cargo.” Enforcing a business rule is domain responsibility and shouldn’t be performed in the application layer.
2.不清楚预订应用程序如何派生企业细分。
2. It isn’t clear how the Booking Application derives the Enterprise Segment.
这两项职责似乎都属于Allocation Checker。改变其接口可以将这两个服务分开,并使交互变得清晰明确。
Both of these responsibilities seem to belong to the Allocation Checker. Changing its interface can separate these two SERVICES and make the interaction clear and explicit.
图 7.11. 域职责从预订应用程序转移到分配检查器
Figure 7.11. Domain responsibilities shifted from Booking Application to Allocation Checker
这种集成带来的唯一严重限制是销售管理系统不得使用分配检查器无法转换为企业段的维度。(如果不应用企业段模式,同样的约束将迫使销售系统仅使用可用于查询货物存储库的维度。这种方法是可行的,但销售系统会蔓延到域的其他部分。在这种设计中,货物存储库只需设计为处理企业段,销售系统中的变化只会影响到分配检查器,而分配检查器最初被认为是一个FACADE 。)
The only serious constraint imposed by this integration will be that the Sales Management System mustn’t use dimensions that the Allocation Checker can’t turn into Enterprise Segments. (Without applying the ENTERPRISE SEGMENT pattern, the same constraint would force the sales system to use only dimensions that can be used in a query to the Cargo Repository. This approach is feasible, but the sales system spills into other parts of the domain. In this design, the Cargo Repository need only be designed to handle Enterprise Segment, and changes in the sales system ripple only as far as the Allocation Checker, which was conceived as a FACADE in the first place.)
尽管分配检查器的接口是与域设计的其他部分有关的唯一部分,但其内部实现可以提供解决性能问题的机会(如果出现)。例如,如果销售管理系统在另一台服务器上运行,可能在另一个位置,则通信开销可能很大,并且每次分配检查都有两次消息交换。除了第二条消息之外没有其他选择,该消息调用销售管理系统来回答是否应接受某种货物的基本问题。但第一条消息(派生货物的企业段)基于与分配决策本身相比相对静态的数据和行为。一种设计选项是缓存这些信息,以便可以使用分配检查器将其重新定位到服务器上,从而将消息传递开销减少一半。这种灵活性是有代价的。设计更加复杂,现在必须以某种方式使重复的数据保持最新。但是,当性能在分布式系统中至关重要时,灵活部署可能是一个重要的设计目标。
Although the Allocation Checker’s interface is the only part that concerns the rest of the domain design, its internal implementation can present opportunities to solve performance problems, if they arise. For example, if the Sales Management System is running on another server, perhaps at another location, the communications overhead could be significant, and there are two message exchanges for each allocation check. There is no alternative to the second message, which invokes the Sales Management System to answer the basic question of whether a certain cargo should be accepted. But the first message, which derives the Enterprise Segment for a cargo, is based on relatively static data and behavior compared to the allocation decisions themselves. One design option would be to cache this information so that it could be relocated on the server with the Allocation Checker, reducing messaging overhead by half. There is a price for this flexibility. The design is more complicated and the duplicated data must now be kept up to date somehow. But when performance is critical in a distributed system, flexible deployment can be an important design goal.
就是这样。这种集成可能会把我们简单、概念一致的设计变成一团乱麻,但现在,使用ANTICORRUPTION LAYER、SERVICE和一些ENTERPRISE SEGMENTS ,我们已经将销售管理系统的功能干净地集成到我们的预订系统中,丰富了领域。
That’s it. This integration could have turned our simple, conceptually consistent design into a tangled mess, but now, using an ANTICORRUPTION LAYER, a SERVICE, and some ENTERPRISE SEGMENTS, we have integrated the functionality of the Sales Management System into our booking system cleanly, enriching the domain.
最后一个设计问题:为什么不让Cargo负责派生企业细分?乍一看,如果派生所基于的所有数据都在 Cargo 中,那么将其作为Cargo的派生属性似乎很优雅。不幸的是,事情并没有那么简单。企业细分的定义是任意的,以按照对业务战略有用的方式进行划分。相同的实体可以出于不同的目的进行不同的细分。我们正在为特定Cargo派生细分以用于预订分配目的,但它可能具有完全不同的企业细分以用于税务会计目的。如果销售管理系统由于新的销售策略而重新配置,甚至分配企业细分也可能会发生变化。因此, Cargo必须了解分配检查器,这远远超出了其概念职责,并且它将承载着派生特定类型企业细分的方法。因此,派生此值的责任应该由了解细分规则的对象承担,而不是具有适用这些规则的数据的对象。这些规则可以拆分成一个单独的“策略”对象,该对象可以传递给Cargo以允许其派生企业细分。该解决方案似乎超出了我们此处的要求,但它将是以后设计的一个选择,并且不应该是一个非常破坏性的改变。
A final design question: Why not give Cargo the responsibility of deriving the Enterprise Segment? At first glance it seems elegant, if all the data the derivation is based on is in the Cargo, to make it a derived attribute of Cargo. Unfortunately, it is not that simple. Enterprise Segments are defined arbitrarily to divide along lines useful for business strategy. The same ENTITIES could be segmented differently for different purposes. We are deriving the segment for a particular Cargo for booking allocation purposes, but it could have a completely different Enterprise Segment for tax accounting purposes. Even the allocation Enterprise Segment could change if the Sales Management System is reconfigured because of a new sales strategy. So the Cargo would have to know about the Allocation Checker, which is well outside its conceptual responsibility, and it would be laden with methods for deriving specific types of Enterprise Segment. Therefore, the responsibility for deriving this value lies properly with the object that knows the rules for segmentation, rather than the object that has the data to which those rules apply. Those rules could be split out into a separate “Strategy” object, which could be passed to a Cargo to allow it to derive an Enterprise Segment. That solution seems to go beyond the requirements we have here, but it would be an option for a later design and shouldn’t be a very disruptive change.
本书第二部分为维护模型与实现之间的对应关系奠定了基础。使用一组经过验证的基本构建块以及一致的语言可以为开发工作带来一定的合理性。
Part II of this book laid a foundation for maintaining the correspondence between model and implementation. Using a proven set of basic building blocks along with consistent language brings some sanity to the development effort.
当然,真正的挑战是找到一个深刻的模型,一个能够捕捉领域专家的细微关注点并推动实际设计的模型。最终,我们希望开发一个能够深刻理解领域的模型。这应该使软件更符合领域专家的思维方式,更能满足用户的需求。本书的这一部分将阐明这一目标,描述实现这一目标的过程,并解释一些设计原则和模式,以使设计满足应用程序和开发人员的需求。
Of course, the real challenge is to actually find an incisive model, one that captures subtle concerns of the domain experts and can drive a practical design. Ultimately, we hope to develop a model that captures a deep understanding of the domain. This should make the software more in tune with the way the domain experts think and more responsive to the user’s needs. This part of the book will clarify that goal, describe the process by which it can be approached, and explain some design principles and patterns to apply to make the design accommodate the needs of the application as well as the developers themselves.
成功开发有用的模型取决于三点。
Success developing useful models comes down to three points.
1.复杂的领域模型是可以实现的,而且值得付出努力。
1. Sophisticated domain models are achievable and worth the trouble.
2.除了通过迭代重构过程之外,它们很少得到开发,包括领域专家与有兴趣了解该领域的开发人员的密切参与。
2. They are seldom developed except through an iterative process of refactoring, including close involvement of the domain experts with developers interested in learning about the domain.
3.它们可能需要复杂的设计技能才能有效实施和使用。
3. They may call for sophisticated design skills to implement and to use effectively.
重构是指在不改变软件功能的情况下对软件进行重新设计。开发人员无需事先做出复杂的设计决策,而是通过一系列连续的、离散的设计更改来处理代码,每次更改都保留现有功能不变,同时使设计更灵活或更易于理解。一套自动化单元测试允许对代码进行相对安全的实验。这个过程让开发人员无需提前考虑。
Refactoring is the redesign of software in ways that do not change its functionality. Rather than making elaborate up-front design decisions, developers take code through a continuous series of small, discrete design changes, each leaving existing functionality unchanged while making the design more flexible or easier to understand. A suite of automated unit tests allows relatively safe experimentation with the code. The process frees the developers from the need to look far ahead.
但几乎所有关于如何重构的文献都侧重于对代码进行机械更改,使其更易于阅读或在非常详细的层面上得到增强。“重构模式”的方法1 当开发人员意识到有机会应用既定的设计模式时,可以为重构过程提供更高级别的目标。不过,这主要是从技术角度来看待设计质量。
But nearly all the literature on how to refactor focuses on mechanical changes to the code that make it easier to read or to enhance at a very detailed level. The approach of “refactoring to patterns”1 can give a higher-level target to the refactoring process when a developer recognizes an opportunity to apply an established design pattern. Still, it is a primarily technical view of the quality of a design.
对系统可行性影响最大的重构是那些由对领域的新见解所激发的重构,或者那些通过代码阐明模型表达的重构。这种类型的重构不会以任何方式取代对设计模式的重构或微重构,后者应该持续进行。它叠加了另一个层次:对更深层次的模型进行重构。基于领域洞察执行重构通常涉及一系列微重构,但动机不仅仅是代码的状态。相反,微重构提供了方便的变更单元,以实现更具洞察力的模型。目标是开发人员不仅可以了解代码的作用;他或她还可以了解它为什么这样做,并将其与与领域专家的持续沟通联系起来。
The refactorings that have the greatest impact on the viability of the system are those motivated by new insights into the domain or those that clarify the model’s expression through the code. This type of refactoring does not in any way replace the refactorings to design patterns or the micro-refactorings, which should proceed continuously. It superimposes another level: refactoring to a deeper model. Executing a refactoring based on domain insight often involves a series of micro-refactorings, but the motivation is not just the state of the code. Rather, the micro-refactorings provide convenient units of change toward a more insightful model. The goal is that not only can a developer understand what the code does; he or she can also understand why it does what it does and can relate that to the ongoing communication with the domain experts.
《重构》 (Fowler 1999)中的目录涵盖了大多数经常出现的微重构。每个微重构主要由代码本身中可以观察到的一些问题所引发。相比之下,随着新见解的出现,领域模型会以多种方式进行转变,以至于不可能编制一份全面的目录。
The catalog in Refactoring (Fowler 1999) covers most of the micro-refactorings that come up regularly. Each is motivated primarily by some problem that can be observed in the code itself. By contrast, domain models are transformed in such a range of ways as new insights emerge that a comprehensive catalog would be impossible to compile.
建模本质上是非结构化的,就像任何探索一样。无论学习和深度思考如何引导,都应该通过重构来获得更深入的洞察。正如第11 章所讨论的那样,已发布的成功模型集合可能会有所帮助,但我们不应该偏离主题,试图将领域建模简化为一本食谱或工具包。建模和设计需要创造力。接下来的六章将提出一些思考改进领域模型的具体方法,以及使其栩栩如生的设计。
Modeling is as inherently unstructured as any exploration. Refactoring to deeper insight should follow wherever learning and deep thinking lead. Published collections of successful models can be helpful, as discussed in Chapter 11, but we shouldn’t get sidetracked trying to reduce domain modeling to a cookbook or a toolkit. Modeling and design call for creativity. The next six chapters will suggest some specific approaches to thinking about improving a domain model, along with the design that brings it to life.
解释对象分析的传统方式是识别需求文档中的名词和动词,并将它们用作初始对象和方法。这种解释被认为是一种过于简单的说法,但对于教授对象建模很有用初学者。但事实是,初始模型通常都是幼稚和肤浅的,基于浅薄的知识。
The traditional way of explaining object analysis involves identifying nouns and verbs in the requirements documents and using them as the initial objects and methods. This explanation is recognized as an oversimplification that can be useful for teaching object modeling to beginners. The truth is, though, that initial models usually are naive and superficial, based on shallow knowledge.
例如,我曾经开发过一个航运应用程序,我最初想到的对象模型涉及船舶和集装箱。船舶从一个地方移动到另一个地方。集装箱通过装载和卸载操作进行关联和分离。这是对一些物理航运活动的准确描述。但事实证明,对于航运业务软件来说,这并不是一个非常有用的模型。
For example, I once worked on a shipping application for which my initial idea of an object model involved ships and containers. Ships moved from place to place. Containers were associated and disassociated through load and unload operations. That is an accurate description of some physical shipping activities. It does not turn out to be a very useful model for shipping business software.
最终,经过数月与航运专家的多次反复合作,我们开发出了一种截然不同的模式。对于外行人来说,这种模式不太明显,但对于专家来说却更为相关。它重新聚焦于货物运输业务。
Eventually, after months working with shipping experts through many iterations, we evolved a quite different model. It was less obvious to a layperson, but much more relevant to the experts. It was refocused on the business of delivering cargo.
船舶仍然存在,但以“船舶航行”的形式抽象出来,即为船舶、火车或其他运输工具安排的特定行程。船舶本身是次要的,可以在最后一刻因维修或时间表延误而替换,而船舶航行则按计划进行。集装箱几乎从模型中消失了。它确实以不同的、非常复杂的形式出现在货物处理应用程序中,但在原始应用程序的上下文中,集装箱是一个操作细节。货物的物理移动让位于货物法律责任的转移。不太明显的对象,如“提单”,则占据了主导地位。
The ships were still there, but abstracted in the form of a “vessel voyage,” a particular trip scheduled for a ship, train, or other carrier. The ship itself was secondary, and could be substituted at the last minute for maintenance or a slipping schedule, while the vessel voyage went on as planned. The shipping container all but disappeared from the model. It did emerge in a cargo-handling application in a different, very complex form, but in the context of the original application, the container was an operational detail. The physical movement of the cargo took a back seat to the transfers of legal responsibility for that cargo. Less obvious objects, such as the “bill of lading,” came to the fore.
每当有新的对象建模者加入项目时,他们的第一建议是什么?缺少的类:船舶和集装箱。他们很聪明。只是还没有经历过发现的过程。
Whenever new object modelers showed up on the project, what was their first suggestion? The missing classes: ship and container. They were smart people. They just hadn’t gone through the processes of discovery.
深度模型清晰地表达了领域专家的主要关注点及其最相关的知识,同时摆脱了领域的表面方面。这个定义没有提到抽象。深度模型通常具有抽象元素,但它也可能具有切中问题核心的具体元素。
A deep model provides a lucid expression of the primary concerns of the domain experts and their most relevant knowledge while it sloughs off the superficial aspects of the domain. This definition doesn’t mention abstraction. A deep model usually has abstract elements, but it may well have concrete elements where those cut to the heart of the problem.
多功能性、简单性和解释力源自真正与领域相符的模型。此类模型几乎总是具有一个特点,即业务专家喜欢使用的简单(尽管可能抽象)的语言。
Versatility, simplicity, and explanatory power come from a model that is truly in tune with the domain. One feature such models almost always have is a simple, though possibly abstract, language that the business experts like to use.
在不断重构的过程中,设计本身需要支持变更。第 10 章介绍了使设计易于使用的方法,无论是对更改设计的人,还是对将设计与系统其他部分集成的人。
In a process of constant refactoring, the design itself needs to support change. Chapter 10 looks at ways to make a design easy to work with, both for those changing it and for those integrating it with other parts of the system.
设计的某些特征使其更易于更改和使用。它们并不复杂,但却具有挑战性。“柔性设计”及其实现方法是第 10 章的主题。
Certain characteristics of a design make it easier to change and use. They are not complicated, but they are challenging. “Supple design” and ways to approach it are the subjects of Chapter 10.
幸运的是,如果每次更改都反映了新的理解,那么反复转换模型和代码这一行为本身就可以在最需要更改的地方带来灵活性,同时还能让常见事情变得简单。一只破旧的手套在手指弯曲的地方会变得柔软,而其他部分则会变得僵硬和具有保护作用。因此,尽管这种建模和设计方法需要反复试验,但更改实际上会变得更容易进行,反复的更改实际上会让我们朝着灵活的设计迈进。
One bit of luck is that the very act of transforming the model and code again and again—if each change reflects new understanding—can bring about flexibility at just the points where change is most needed, along with easy ways of doing the common things. A well-worn glove becomes supple at the points where the fingers bend, while other parts are stiff and protective. So although there is a lot of trial and error involved in this approach to modeling and design, the changes can actually become easier to make, and the repeated changes actually move us toward a supple design.
除了促进变革之外,灵活的设计还有助于改进模型本身。模型驱动设计有两条腿。深度模型使富有表现力的设计成为可能。同时,当设计具有让开发人员进行实验的灵活性和向开发人员展示正在发生的事情的清晰度时,它实际上可以为模型发现过程提供洞察力。反馈回路的这一半至关重要,因为我们正在寻找的模型不仅仅是一套好的想法:它是系统的基础。
In addition to facilitating change, a supple design contributes to the refinement of the model itself. A MODEL-DRIVEN DESIGN stands on two legs. A deep model makes possible an expressive design. At the same time, a design can actually feed insight into the model discovery process when it has the flexibility to let a developer experiment and the clarity to show a developer what is happening. This half of the feedback loop is essential, because the model we are looking for is not just a nice set of ideas: it is the foundation of the system.
要创建真正适合手头问题的设计,首先必须有一个模型来捕捉领域的核心相关概念。积极寻找这些概念并将它们带入设计是第 9 章“使隐含概念显化”的主题。
To create a design really fitted to the problem at hand, you must first have a model that captures the central relevant concepts of the domain. Actively searching for these concepts and bringing them into the design is the subject of Chapter 9, “Making Implicit Concepts Explicit.”
由于模型和设计之间的密切关系,当代码难以重构时,建模过程就会停止。第 10 章“柔性设计”讨论了如何为软件开发人员(尤其是您自己)编写软件,以便能够高效地扩展和变化。这项工作与模型的进一步改进齐头并进。它通常需要更先进的设计技术和更严格的模型定义。
Because of the close relationship between model and design, the modeling process comes to a halt when the code is hard to refactor. Chapter 10, “Supple Design,” discusses how to write software for software developers, not least yourself, so that it is productive to extend and change. This effort goes hand in hand with further refinements to the model. It often entails more advanced design techniques and more rigor in model definitions.
您通常会依靠创造力和反复试验来找到建模您发现的概念的好方法,但有时有人已经制定了您可以遵循的模式。第 11 章和第 12章讨论了“分析模式”和“设计模式”的应用。这些模式不是现成的解决方案,但它们可以为您的知识处理过程提供信息并缩小您的搜索范围。
You will usually depend on creativity and trial and error to find good ways to model the concepts you discover, but sometimes someone has laid down a pattern you can follow. Chapters 11 and 12 discuss the application of “analysis patterns” and “design patterns.” Such patterns are not ready-made solutions, but they feed your knowledge crunching process and narrow your search.
但我将从领域驱动设计中最激动人心的事件开始第三部分。有时,当舞台上设置了模型驱动设计和明确的概念时,您就会取得突破。一个机会出现了,可以将您的软件转变为比您预期的更具表现力和多功能性的东西。这可能意味着新功能,也可能只是意味着用简单、灵活的更深层模型表达来替换一大块僵化的代码。虽然这样的突破并不是每天都会发生,但它们是如此宝贵,以至于当它们真的发生时,需要认识到并抓住机会。
But I’ll start Part III with the most exciting event in domain-driven design. Sometimes, when the stage is set with a MODEL-DRIVEN DESIGN and explicit concepts, you have a breakthrough. An opportunity opens up to transform your software into something more expressive and versatile than you expected. This can mean new features or it can just mean the replacement of a big chunk of rigid code with a simple, flexible expression of a deeper model. Although such breakthroughs don’t come along every day, they are so valuable that when they do happen, the opportunity needs to be recognized and grasped.
第 8 章讲述了一个项目的真实故事,该项目通过重构来获得更深入的见解,并取得了突破。这种体验不是你可以计划的。尽管如此,它为思考领域重构提供了一个很好的背景。
Chapter 8 tells the true story of a project on which a process of refactoring toward deeper insight led to a breakthrough. This experience is not something you can plan for. Nonetheless, it provides a good context for thinking about domain refactoring.
重构带来的回报并不是线性的。通常,小努力就能带来边际回报,而小改进会累积起来。它们可以对抗熵,是防止遗留问题僵化的第一线保护。但一些最重要的见解会突然出现,并给整个项目带来冲击。
The returns from refactoring are not linear. Usually there is a marginal return for a small effort, and the small improvements add up. They fight entropy, and they are the frontline protection against a fossilized legacy. But some of the most important insights come abruptly and send a shock through the project.
团队会缓慢但稳步地吸收知识并将其转化为模型。深度模型可以通过一系列小重构逐渐出现,每次重构一个对象:这里调整关联,那里转移职责。
Slowly but surely, the team assimilates knowledge and crunches it into a model. Deep models can emerge gradually through a sequence of small refactorings, an object at a time: a tweaked association here, a shifted responsibility there.
然而,持续的重构往往会为一些不那么有序的事情铺平道路。代码和模型的每一次改进都会让开发人员有更清晰的视野。这种清晰度为突破洞察力。变革的浪潮催生出一种更深层次地与用户的现实和优先事项相对应的模型。即使复杂性消失,多功能性和解释力也会突然增加。
Often, though, continuous refactoring prepares the way for something less orderly. Each refinement of code and model gives developers a clearer view. This clarity creates the potential for a breakthrough of insights. A rush of change leads to a model that corresponds on a deeper level to the realities and priorities of the users. Versatility and explanatory power suddenly increase even as complexity evaporates.
这种突破不是一种技术,而是一种事件。挑战在于认识到正在发生的事情并决定如何处理它。为了传达这种体验的感受,我将讲述我几年前参与的一个项目的真实故事,以及我们如何得出一个非常有价值的深度模型。
This sort of breakthrough is not a technique; it is an event. The challenge lies in recognizing what is happening and deciding how to deal with it. To convey what this experience feels like, I’ll tell a true story of a project I worked on some years ago, and how we arrived at a very valuable deep model.
经过纽约漫长的冬天的重构,我们终于找到了一个模型,它囊括了该领域的一些关键知识,并且设计出了一种能为应用程序带来实际作用的设计。我们正在开发一个大型应用程序的核心部分,用于管理一家投资银行的银团贷款。
After a long New York winter of refactoring, we had arrived at a model that captured some of the key knowledge of the domain and a design that did some real work for the application. We were developing a core part of a large application for managing syndicated loans in an investment bank.
当英特尔想要建造一座耗资 10 亿美元的工厂时,他们需要的贷款额太大,任何一家贷款公司都无法承担,因此贷款方会组成一个财团,汇集资源来支持工厂建设(见边栏)。投资银行通常充当财团领导者,协调交易和其他服务。我们的项目是开发软件来跟踪和支持整个过程。
When Intel wants to build a billion-dollar factory, they need a loan that is too big for any single lending company to take on, so the lenders form a syndicate that pools its resources to support a facility (see sidebar). An investment bank usually acts as syndicate leader, coordinating transactions and other services. Our project was to build software to track and support this whole process.
我们感觉很好。四个月前,我们陷入了困境,因为代码库完全无法运行,而且是继承的,后来我们才将其整合成一个连贯的模型驱动设计。
We were feeling pretty good. Four months before, we had been in deep trouble with a completely unworkable, inherited code base, which we had since wrestled into a coherent MODEL-DRIVEN DESIGN.
图 8.1中反映的模型使常见情况变得非常简单。贷款投资是一个派生对象,它表示特定投资者对贷款的贡献,与其在设施中的份额成比例。
The model reflected in Figure 8.1 makes the common case very simple. The Loan Investment is a derived object that represents a particular investor’s contribution to the Loan, proportional to its share in the Facility.
图 8.1. 假设贷款人份额固定的模型
Figure 8.1. A model that assumes lender shares are fixed
但还是有一些令人不安的迹象。我们不断遇到意想不到的要求,这些要求使得设计变得复杂。一个主要的例子是,人们逐渐意识到,一项贷款中的股份只是参与任何特定贷款提取的指导方针。当借款人申请贷款时,银团的领导人会要求所有成员提供他们的股份。
But there were some disconcerting signs. We kept stumbling over unexpected requirements that complicated the design. A major example was the creeping understanding that the shares in a Facility were only a guideline to participation in any particular loan draw-down. When the borrower requests its money, the leader of the syndicate calls all members for their shares.
投资者通常会在被要求时拿出自己的份额,但他们通常会与财团的其他成员协商,减少(或增加)投资。我们通过在模型中添加贷款调整来适应这种情况。
When called, the investors usually cough up their share, but often they negotiate with other members of the syndicate and invest less (or more). We had accommodated this by adding Loan Adjustments to the model.
图 8.2. 逐步改变以解决问题的模型。贷款调整跟踪贷款人最初在贷款安排中同意的份额的偏离情况。
Figure 8.2. A model incrementally changed to solve problems. Loan Adjustments track departures from the share a lender originally agreed to in the Facility.
这种改进让我们能够跟上各种交易规则的步伐,使其变得更加清晰。但复杂性正在增加,我们似乎无法快速收敛到真正可靠的功能。
Refinements of this kind allowed us to keep up as the rules of various transactions became clearer. But complexity was increasing, and we did not seem to be converging quickly onto really solid functionality.
更令人不安的是微妙的舍入不一致,我们无法用越来越复杂的算法来消除这种不一致。确实,在一笔 1 亿美元 (MM) 的交易中,没有人关心多余的钱去了哪里,但银行家们不信任无法仔细核算这些便士的去向。我们开始怀疑,我们遇到的困难是基本设计问题的表现。
Even more troubling were subtle rounding inconsistencies that we had not been able to squash with increasingly complex algorithms. True, in a $100 million (MM) deal, no one cares about where the extra pennies go, but bankers don’t trust software that cannot meticulously account for those pennies. We began to suspect that our difficulties were symptomatic of a basic design problem.
突然,有一周,我们意识到了问题所在。我们的模型将设施和贷款份额以一种不适合业务的方式捆绑在一起。这一发现产生了广泛的影响。在业务专家的点头和热情帮助下——我敢说,他们想知道我们为什么花了这么长时间——我们在白板上制定了一个新模型。虽然细节尚未确定,但我们知道新模型的关键特征:贷款份额和设施份额可以独立变化。有了这种洞察力,我们使用新模型的可视化演示了许多场景,如下所示:
Suddenly one week it dawned on us what was wrong. Our model tied together the Facility and Loan shares in a way that was not appropriate to the business. This revelation had wide repercussions. With the business experts nodding, enthusiastically helping—and, I dare say, wondering what took us so long—we hashed out a new model on a whiteboard. Although the details hadn’t jelled yet, we knew the crucial feature of the new model: shares of the Loan and those of the Facility could change independently of each other. With that insight, we walked through numerous scenarios using a visualization of the new model that looked something like this:
图 8.3. 根据设施份额分布的回撤
Figure 8.3. A drawdown distributed based on Facility shares
该图表明借款人选择从贷款协议下承诺的 1 亿美元中提取 5,000 万美元的初始资金。三家贷款机构按照贷款协议份额的准确比例提供各自的份额,从而产生一笔 5,000 万美元的贷款,由贷款机构分担。
This diagram says that the borrower has chosen to draw an initial $50MM from the $100MM committed under the Facility. The three lenders chip in their shares in exact proportion to the Facility shares, resulting in a $50MM Loan divided among the lenders.
然后,在图 8.4中,借款人额外提取 3000 万美元,使其未偿还贷款达到 8000 万美元,仍低于该贷款的 1 亿美元限额。这次,B 公司选择不参与,让 A 公司获得额外的份额。提取的份额反映了这些投资选择。当提取的金额添加到贷款中时,贷款的份额不再与贷款的份额成比例。这是很常见的。
Then, in Figure 8.4, the borrower draws an additional $30MM, bringing his outstanding Loan to $80MM, still under the $100MM limit of the Facility. This time, Company B chooses not to participate, letting Company A take an extra share. The shares of the draw-down reflect these investment choices. When the drawdown amounts are added to the Loan, the shares of the Loan are no longer proportional to the shares of the Facility. This is common.
图 8.4.贷款人 B 选择不进行第二次提款。
Figure 8.4. Lender B opts out of a second drawdown.
图 8.5. 本金支付总是与未偿还贷款的份额按比例分配。
Figure 8.5. Principal payments are always distributed proportional to shares in the outstanding Loan.
当借款人偿还贷款时,资金将按照贷款份额(而不是贷款额)在贷款人之间分配。同样,利息支付也将按照贷款份额进行分配。
When the borrower pays down the Loan, the money is divided among the lenders according to the shares of the Loan, not the Facility. Likewise, interest payments will be divided according to the Loan shares.
图 8.6. 费用支付总是按设施股份比例分配。
Figure 8.6. Fee payments are always distributed proportionally to shares in the Facility.
另一方面,当借款人为获得贷款而支付费用时,这笔钱将根据贷款份额进行分配,而不管实际借款人是谁。贷款不会因支付费用而改变。甚至有些情况下,贷款人将费用份额与其利息份额分开交易,等等。
On the other hand, when the borrower pays a fee for the privilege of having the Facility available, this money is divided according to the Facility shares, regardless of who actually has lent money. The Loan is unchanged by fee payments. There are even scenarios in which lenders trade shares of fees separately from their shares of interest, and so on.
我们有两个深刻的领悟。首先,我们意识到我们的“投资”和“贷款投资”只是一个普遍而基本的概念的两个特例:股份。设施股份、贷款股份、付款分配股份……股份,无处不在的股份。任何可分割价值的股份。
We had two deep insights. First was the realization that our “Investments” and “Loan Investments” were just two special cases of a general and fundamental concept: shares. Shares of a facility, shares of a loan, shares of a payment distribution. Shares, shares everywhere. Shares of any divisible value.
几天后的动荡之中,我绘制了一个股票模型,借鉴了与专家讨论时使用的语言和我们共同探索的情景。
A few tumultuous days later I had sketched a model of shares, drawing on the language used in the discussions with experts and the scenarios we had explored together.
图 8.7. 股份的抽象模型
Figure 8.7. An abstract model of shares
我还草拟了一个与之相配合的新贷款模型。
I also sketched a new loan model to go with it.
图 8.8.使用Share Pie 的贷款模型
Figure 8.8. The Loan model using Share Pie
不再有专门用于Facility或Loan的股份的对象。它们都被分解为更直观的“股份饼”。这种概括允许引入“股份数学”,大大简化了任何交易中股份的计算,并使这些计算更具表现力、更简洁且更易于组合。
There were no longer specialized objects for the shares of a Facility or a Loan. They both were broken down into the more intuitive “Share Pie.” This generalization allowed the introduction of “shares math,” vastly simplifying the calculation of shares in any transaction, and making those calculations more expressive, concise, and easily combined.
但最重要的是,问题消失了,因为新模型消除了不适当的约束。它允许贷款份额脱离设施份额的比例,同时保留总额、费用分配等的有效约束。贷款份额可以直接调整,因此不再需要贷款调整,并且消除了大量特殊情况逻辑。
But most of all, problems went away because the new model removed an inappropriate constraint. It freed the Loan’s Shares to depart from the proportions of the Facility’s Shares, while keeping in place the valid constraints on totals, fee distributions, and so on. The Share Pie of the Loan could be adjusted directly, so the Loan Adjustment was no longer needed, and a large amount of special-case logic was eliminated.
贷款投资消失了,这时我们意识到“贷款投资”不是一个银行术语。事实上,业务专家曾多次告诉我们他们不理解它。他们听从了我们的软件知识,并认为它对技术设计有用。实际上,我们是根据对领域的不完全理解而创建的。
The Loan Investment had disappeared, and at this point we realized that “loan investment” was not a banking term. In fact, the business experts had told us a number of times that they didn’t understand it. They had deferred to our software knowledge and assumed it was useful to the technical design. Actually, we had created it based on our incomplete understanding of the domain.
突然之间,基于这种看待领域的新方式,我们可以相对轻松地、比以前更简单地处理我们曾经遇到过的所有场景。我们的模型图对业务专家来说非常有意义,他们经常表示这些图对他们来说“太技术化了”。即使只是在白板上画草图,我们也可以看到我们最顽固的舍入问题将被连根拔起,从而让我们可以废弃一些复杂的舍入代码。
Suddenly, on the basis of this new way of looking at the domain, we could run through every scenario we had ever encountered relatively effortlessly, much more simply than ever before. And our model diagrams made perfect sense to the business experts, who had often indicated that the diagrams were “too technical” for them. Even just sketching on a whiteboard, we could see that our most persistent rounding problems would be pulled out by the roots, allowing us to scrap some of the complicated rounding code.
我们的新模型运行良好。真的非常好。
Our new model worked well. Really, really well.
我们都感觉不舒服!
And we all felt sick!
你可能会合理地认为,此时此刻,我们本应欣喜若狂。但我们没有。我们面临着一个紧迫的最后期限;该项目已经严重落后于计划。我们的主要情绪是恐惧。
You might reasonably assume that we would have been elated at this point. We were not. We were under a severe deadline; the project was already dangerously behind schedule. Our dominant emotion was fear.
重构的好处是,你总是循序渐进,始终保持一切正常。但要将我们的代码重构为这种新模型,需要更改大量支持代码,而且中间几乎没有稳定的停止点。我们可以看到可以做出一些小改进,但没有一个能让我们更接近新概念。我们可以看到实现目标的一系列小步骤,但应用程序的某些部分会在此过程中被禁用。这是在自动化测试广泛用于此类项目之前。我们没有自动化测试,所以必然会出现无法预见的故障。
The gospel of refactoring is that you always go in small steps, always keeping everything working. But to refactor our code to this new model would require changing a lot of supporting code, and there would be few, if any, stable stopping points in between. We could see some small improvements we could make, but none that would take us closer to the new concept. We could see a sequence of small steps to get there, but parts of the application would be disabled along the way. And this was before the age when automated tests were widely used on such projects. We had none, so there was bound to be unforeseen breakage.
这需要付出努力。几个月的努力让我们疲惫不堪。
And it was going to take effort. We were already exhausted from months of pushing.
此时,我们与项目经理举行了一次我永远不会忘记的会议。我们的经理是一个聪明而大胆的人。他问了一系列问题:
At this point, we had a meeting with our project manager that I will never forget. Our manager was an intelligent and bold man. He asked a series of questions:
Q: How long would it take to get back to current functionality with the new design?
Q: Could we solve the problems without it?
A: Probably. But no way to be sure.
Q: Would we be able to move forward in the next release if we didn’t do it now?
答:如果没有改变,前进的步伐就会很慢。一旦我们有了安装基础,改变就会困难得多。
A: Forward movement would be slow without the change. And the change would be much harder once we had an installed base.
Q: Did we think it was the right thing to do?
答:我们知道政治局势不稳定,所以如果有必要,我们会应对。我们也很累。但是,是的,这是一个更简单的解决方案,更适合业务。从长远来看,风险较低。
A: We knew the political situation was unstable, so we’d cope if we had to. And we were tired. But, yes, it was a simpler solution that fit the business much better. In the long run it was lower risk.
他给了我们批准,并告诉我们他会承受压力。我一直非常钦佩他做出这个决定所需的勇气和信任。
He gave us the go-ahead and told us he would handle the heat. I’ve always had tremendous admiration for the courage and trust it took for him to make that decision.
我们拼尽全力,用了三周时间就完成了。这是一项大工程,但进展出奇地顺利。
We busted our butts and got it done in three weeks. It was a big job, but it went surprisingly smoothly.
令人费解的意外需求变化停止了。舍入逻辑虽然从未变得简单,但已经稳定下来,并且意义重大。我们交付了第一版,第二版的开发之路也变得清晰起来。我差点儿精神崩溃。
The mystifyingly unexpected requirement changes stopped. The rounding logic, though never exactly simple, stabilized and made sense. We delivered version one and the way was clear to version two. My nervous breakdown was narrowly averted.
随着第二版的发展,Share Pie成为整个应用程序的统一主题。技术人员和业务专家使用它来讨论系统。营销人员使用它向潜在客户解释功能。这些潜在客户和客户立即掌握了它并用它来讨论功能。它真正成为了UBIQUITOUS LANGUAGE的一部分,因为它触及了贷款银团的核心。
As version two evolved, this Share Pie became the unifying theme of the whole application. Technical people and business experts used it to discuss the system. Marketing people used it to explain the features to prospective customers. Those prospects and customers immediately grasped it and used it to discuss features. It truly became part of the UBIQUITOUS LANGUAGE because it got to the heart of what loan syndication is about.
当突破更深层模型的前景出现时,人们往往会感到害怕。与大多数重构相比,这种改变具有更高的机会和风险。而且时机可能不合适。
When the prospect of a breakthrough to a deeper model presents itself, it is often scary. Such a change has higher opportunity and higher risk than most refactorings. And timing may be inopportune.
尽管我们可能希望事情不是一帆风顺的,但进展并非一帆风顺。过渡到真正深度的模型需要思维上的深刻转变,并且需要对设计进行重大改变。在许多项目中,模型和设计中最重要的进展都来自这些突破。
Much as we might like it to be otherwise, progress isn’t a smooth ride. The transition to a really deep model is a profound shift in your thinking and demands a major change to the design. On many projects the most important progress in model and design come in these breakthroughs.
不要因为试图取得突破而陷入瘫痪。突破的可能性通常来自多次适度的重构。大部分时间都花在进行零碎的改进上,模型见解在每次连续的改进过程中逐渐显现。
Don’t become paralyzed trying to bring about a breakthrough. The possibility usually comes after many modest refactorings. Most of the time is spent making piecemeal improvements, with model insights emerging gradually during each successive refinement.
为实现突破奠定基础,需要集中精力处理知识并培养强大的通用语言。探索重要领域概念,并在模型中明确它们(如第 9 章所述)。优化设计,使其更加灵活(参见第 10 章)。提炼模型(参见第 15 章)。推动这些更可预测的杠杆,以增加清晰度——通常是突破的先兆。
To set the stage for a breakthrough, concentrate on knowledge crunching and cultivating a robust UBIQUITOUS LANGUAGE. Probe for important domain concepts and make them explicit in the model (as discussed in Chapter 9). Refine the design to be suppler (see Chapter 10). Distill the model (see Chapter 15). Push on these more predictable levers, which increase clarity—usually a precursor of breakthroughs.
不要放弃适度的改进,即使局限于相同的一般概念框架内,这些改进也会逐渐深化模型。不要因为看得太远而陷入困境。只要留意机会就行了。
Don’t hold back from modest improvements, which gradually deepen the model, even if confined within the same general conceptual framework. Don’t be paralyzed by looking too far forward. Just be watchful for the opportunity.
这一突破让我们走出了困境,但这并不是故事的结束。更深层次的模型带来了意想不到的机会,使应用程序更加丰富,设计更加清晰。
That breakthrough got us out of the woods, but it was not the end of the story. The deeper model opened unexpected opportunities to make the application richer and the design clearer.
在Share Pie版软件发布几周后,我们注意到模型中另一个尴尬的方面使设计变得复杂。一个重要的ENTITY缺失了,它的缺失使得其他对象需要承担额外的责任。具体来说,有大量的规则来管理贷款提取、费用支付等,所有这些逻辑都塞进了Facility和Loan 的各种方法中。这些设计问题在Share Pie取得突破之前几乎不引人注意,但随着我们的视野更加清晰,这些问题变得显而易见。现在,我们注意到讨论中出现了一些在模型中根本找不到的术语,例如“交易”(即金融交易)等术语,我们开始意识到所有这些复杂的方法都隐含着这些术语。
Just weeks after the release of the Share Pie version of the software, we noticed another awkward aspect of the model that was complicating the design. An important ENTITY was missing, its absence leaving extra responsibilities to be taken up by other objects. Specifically, there were significant rules governing loan drawdowns, fee payments, and so on, and all this logic was crammed into various methods on the Facility and Loan. These design problems, which had been barely noticeable before the Share Pie breakthrough, became obvious with our clearer field of vision. Now we noticed terms popping up in our discussions that were nowhere to be found in the model—terms such as “transaction” (meaning a financial transaction)—that we started to realize were being implied by all those complicated methods.
遵循与前面描述的过程类似的过程(尽管幸运的是,时间压力要小得多),又带来了一轮洞察,并建立了一个更深层次的模型。这个新模型将这些隐含的概念明确化,作为交易类型,同时简化了头寸(包括设施和贷款的抽象)。我们很容易定义各种交易以及他们的规则、谈判程序和批准流程,所有这些都以相对不言自明的代码编写而成。
Following a process similar to the one described earlier (although, thankfully, under much less time pressure) led to yet another round of insights and a still deeper model. This new model made those implicit concepts explicit, as kinds of Transactions, and at the same time simplified the Positions (an abstraction including the Facility and Loan). It became easy to define the diverse transactions we had, along with their rules, negotiating procedures, and approval processes, and all in relatively self-explanatory code.
图 8.9。几周后出现的另一个模型突破。交易约束可以轻松精确地表达。
Figure 8.9. Another model breakthrough that followed several weeks later. Constraints on Transactions could be expressed with easy precision.
就像深度模型取得真正突破之后经常出现的情况一样,新设计的清晰度和简单性,加上基于新的UBIQUITOUS LANGUAGE的增强通信,带来了又一次建模突破。
As is often the case after a real breakthrough to a deep model, the clarity and simplicity of the new design, combined with the enhanced communication based on the new UBIQUITOUS LANGUAGE, had led to yet another modeling breakthrough.
当大多数项目因已建内容的庞大和复杂而开始陷入困境时,我们的开发步伐却在加快。
Our pace of development was accelerating at a stage where most projects are beginning to bog down in the mass and complexity of what has already been built.
深度建模听起来很棒,但实际上该怎么做呢?深度模型之所以强大,是因为它包含核心概念和抽象,可以简洁灵活地表达用户活动、问题和解决方案的基本知识。第一步是以某种方式在模型中表示领域的基本概念。在经过连续迭代的知识处理和重构之后,细化才会出现。但是,当一个重要概念在模型和设计中被识别并明确表达时,这个过程才真正开始。
Deep modeling sounds great, but how do you actually do it? A deep model has power because it contains the central concepts and abstractions that can succinctly and flexibly express essential knowledge of the users’ activities, their problems, and their solutions. The first step is to somehow represent the essential concepts of the domain in the model. Refinement comes later, after successive iterations of knowledge crunching and refactoring. But this process really gets into gear when an important concept is recognized and made explicit in the model and design.
当开发人员识别出讨论中暗示或设计中隐含存在的概念,然后用一个或多个对象或关系在模型中明确地表示它时,领域模型和相应的代码就会发生许多转换。
Many transformations of domain models and the corresponding code happen when developers recognize a concept that has been hinted at in discussion or present implicitly in the design, and they then represent it explicitly in the model with one or more objects or relationships.
有时,将以前隐含的概念转化为显含的概念是一种突破,可以带来深层模型。但更常见的情况是,突破是在模型中明确了许多重要概念之后出现的;在连续的重构反复调整了它们的职责、改变了它们与其他对象的关系,甚至更改了几次名称之后出现的。一切最终都清晰起来。但这个过程始于以某种形式识别隐含的概念,无论形式多么粗糙。
Occasionally, this transformation of a formerly implicit concept into an explicit one is a breakthrough that leads to a deep model. More often, though, the breakthrough comes later, after a number of important concepts are explicit in the model; after successive refactorings have tweaked their responsibilities repeatedly, changed their relationships with other objects, and even changed their names a few times. Everything finally snaps into focus. But the process starts with recognizing the implied concepts in some form, however crude.
开发人员必须对那些揭示潜在隐含概念的线索保持敏感,有时他们必须主动寻找这些线索。大多数此类发现都来自于倾听团队的语言、仔细审查设计中的尴尬之处和专家陈述中看似矛盾的地方、挖掘领域文献以及进行大量的实验。
Developers have to sensitize themselves to the hints that reveal lurking implicit concepts, and sometimes they have to proactively search them out. Most such discoveries come from listening to the language of the team, scrutinizing awkwardness in the design and seeming contradictions in the statements of experts, mining the literature of the domain, and doing lots and lots of experimentation.
您可能还记得这样的经历:用户总是谈论报告中的某个项目。该项目是从各种对象的属性甚至可能是直接的数据库查询编译而来的。相同的数据集在应用程序的另一部分组装,以便呈现、报告或导出某些内容。但您从未看到对对象的需要。可能您从未真正理解用户对特定术语的含义,也没有意识到它的重要性。
You may remember an experience like this: The users have always talked about some item on a report. The item is compiled from attributes of various objects and maybe even a direct database query. The same data set is assembled in another part of the application in order to present or report or derive something. But you have never seen the need for an object. Probably, you have never really understood what the users meant by a particular term and had not realized it was important.
然后,你突然灵光一闪。报告上的项目名称代表了一个重要的领域概念。你兴奋地与专家讨论你的新见解。也许他们对你终于明白了这一点感到如释重负。也许他们打哈欠,因为他们一直认为这是理所当然的。无论哪种情况,你开始在黑板上画模型图,代替你以前经常做的手势。用户纠正了你关于新模型连接方式的细节,但你可以看出讨论的质量发生了变化。你和用户彼此理解得更准确,解决特定场景的模型交互演示也变得更加自然。领域模型的语言变得更加强大。你重构代码以反映新模型,发现你的设计更简洁。
Then suddenly a light comes on in your head. The name of the item on that report designates an important domain concept. You talk excitedly with your experts about your new insight. Maybe they show relief that you finally got it. Maybe they yawn because they’ve taken it for granted all along. Either way, you start to draw model diagrams on the board that fill in for some hand waving that you’ve always done before. The users correct you on the details of how the new model connects, but you can tell that there is a change in the quality of the discussion. You and the users understand each other more precisely, and demonstrations of model interactions to solve specific scenarios have become more natural. The language of the domain model has become more powerful. You refactor the code to reflect the new model and find you have a cleaner design.
听听领域专家使用的语言。是否有简洁地陈述复杂事物的术语?他们是否在纠正你的用词选择(也许是外交上的)?当你使用某个特定短语时,他们脸上的困惑表情会消失吗?这些都是可能对模型有益的概念的暗示。
Listen to the language the domain experts use. Are there terms that succinctly state something complicated? Are they correcting your word choice (perhaps diplomatically)? Do the puzzled looks on their faces go away when you use a particular phrase? These are hints of a concept that might benefit the model.
这不是旧的“名词是对象”的概念。听到一个新词会产生一个线索,然后你通过对话来跟进,知识拼凑,目的是提出一个清晰、有用的概念。当用户或领域专家使用设计中不存在的词汇时,这是一个警告信号。当开发人员和领域专家都在使用设计中不存在的术语时,这是一个双重强烈的警告。
This is not the old “nouns are objects” notion. Hearing a new word produces a lead, which you follow up with conversation and knowledge crunching, with the goal of carving out a clean, useful concept. When the users or domain experts use vocabulary that is nowhere in the design, that is a warning sign. It is a doubly strong warning when both the developers and the domain experts are using terms that are not in the design.
或者也许最好将其视为一个机会。UBIQUITOUS LANGUAGE由遍布语音、文档、模型图甚至代码的词汇组成。如果设计中缺少某个术语,那么通过将其包括在内,这是一个改进模型和设计的机会。
Or perhaps it is better to look at it as an opportunity. The UBIQUITOUS LANGUAGE is made up of the vocabulary that pervades speech, documents, model diagrams, and even code. If a term is absent from the design, it is an opportunity to improve the model and design by including it.
该团队已经开发出一款可以预订货物的应用。他们正着手开发一款“运营支持”应用,帮助处理起运地和目的地以及船舶转运时货物装卸的工作订单。
The team had already developed a working application that could book a cargo. They were starting to build an “operations support” application that would help juggle the work orders for loading and unloading cargos at the origin and destination and at transfers between ships.
订舱应用程序使用路线引擎来规划货物的行程。行程的每一段都存储在数据库表的一行中,标明计划运载货物的船舶航程(特定船舶的特定航程)的 ID、装货地点和卸货地点。
The booking application used a routing engine to plan the trip for a cargo. Each leg of the journey was stored in a row of a database table, indicating the ID of the vessel voyage (a particular voyage by a particular ship) slated to carry the cargo, the location where it would be loaded, and the location where it would be unloaded.
图 9.1
Figure 9.1
让我们偷听一下开发人员和航运专家之间的对话(高度简短)。
Let’s eavesdrop on a conversation (heavily abbreviated) between the developer and a shipping expert.
开发人员:我想确保“货物预订”表具有运营应用程序所需的所有数据。
Developer: I want to make sure the “cargo bookings” table has all the data that the operations application will need.
专家:他们需要货物的整个行程。现在有哪些信息?
Expert: They’re going to need the whole itinerary for the Cargo. What information does it have now?
开发者:每段货物的ID、船舶航程、装货港、卸货港。
Developer: The cargo ID, the vessel voyage, the loading port, and the unloading port for each leg.
专家:日期呢?运营部门需要根据预计的时间来承包搬运工作。
Expert: What about the date? Operations will need to contract handling work based on the expected times.
开发人员:嗯,这可以从船期表中推导出来。表格数据是标准化的。
Developer: Well, that can be derived from the schedule of the vessel voyage. The table data is normalized.
专家:是的,需要日期是正常的。运营人员使用这类行程来规划接下来的搬运工作。
Expert: Yes, it is normal to need the date. Operations people use these kinds of itineraries to plan for upcoming handling work.
开发人员:是的……好的,他们肯定可以查看日期。运营管理应用程序将能够提供整个装卸顺序,以及每个装卸操作的日期。我想你会说是“行程”。
Developer: Yeah . . . OK, they’ll definitely have access to the dates. The operations management application will be able to provide the whole loading and unloading sequence, with the date of each handling operation. The “itinerary,” I guess you would say.
专家:很好。行程单是他们需要的主要内容。实际上,你知道,预订应用程序有一个菜单项,可以打印行程单或通过电子邮件发送给客户。你能以某种方式使用它吗?
Expert: Good. The itinerary is the main thing they’ll need. Actually, you know, the booking application has a menu item that will print an itinerary or e-mail it to the customer. Can you use that somehow?
开发人员:我认为那只是一份报告。我们无法以此为基础开发操作应用程序。
Developer: That’s just a report, I think. We won’t be able to base the operations application on that.
[开发人员看起来若有所思,然后兴奋起来。 ]
[Developer looks thoughtful, then excited.]
开发人员:所以,这个行程实际上就是预订和运营之间的纽带。
Developer: So, this itinerary is really the link between booking and operations.
专家:是的,还有一些客户关系。
Expert: Yes, and some customer relations, too.
开发人员: [在白板上画一张图。 ] 那么你会说它是这样的吗?
Developer: [Sketching a diagram on the whiteboard.] So would you say it is something like this?
图 9.2
Figure 9.2
专家:是的,看起来基本正确。对于每一段航程,您需要查看船舶航程、装卸地点和时间。
Expert: Yes, that looks basically right. For each leg you’d like to see the vessel voyage, the load and unload location, and time.
开发人员:因此,一旦我们创建了Leg对象,它就可以从船舶航行时间表中得出时间。我们可以将Itinerary对象作为我们与运营应用程序的主要联系点。我们可以重写该行程报告以使用它,这样我们就可以把域逻辑放回到域层中。
Developer: So once we create the Leg object, it can derive the times from the vessel voyage schedule. We can make the Itinerary object our main point of contact with the operations application. And we can rewrite that itinerary report to use this, so we’ll get the domain logic back into the domain layer.
专家:我并没有完全理解这些,但您说得对,行程表的两个主要用途是在预订报告中和运营应用程序中。
Expert: I didn’t follow all of that, but you are right that the two main uses for the Itinerary are in the report in booking and in the operations application.
开发人员:嘿!我们可以让路线服务接口返回行程对象,而不是将数据放入数据库表中。这样,路线引擎就不需要知道我们的表了。
Developer: Hey! We can make the Routing Service interface return an itinerary object instead of putting the data in the database table. That way the routing engine doesn’t need to know about our tables.
专家:啊?
Expert: Huh?
开发人员:我的意思是,我将让路线引擎只返回行程。然后,当预订的其余部分保存时,预订应用程序就可以将其保存在数据库中。
Developer: I mean, I’ll make the routing engine just return an Itinerary. Then it can be saved in the database by the booking application when the rest of the booking is saved.
专家:你的意思是现在不是这样了?!
Expert: You mean it isn’t that way now?!
然后,开发人员开始与参与路线规划过程的其他开发人员进行交流。他们讨论了模型的变更以及对设计的影响,并在必要时请航运专家帮忙。他们绘制了图 9.3中的图表。
The developer then went off to talk with the other developers involved in the routing process. They hashed out the changes to the model and the implications for the design, calling on the shipping experts when needed. They came up with the diagram in Figure 9.3.
图 9.3
Figure 9.3
接下来,开发人员重构了代码以反映新模型。他们在一周内完成了两到三次重构,但速度很快,除了简化预订应用程序中的行程报告外,他们在接下来的一周初就完成了这项工作。
Next, the developers refactored the code to reflect the new model. They did it in a series of two or three refactorings, but in quick succession, within a week, except for simplifying the itinerary report in the booking application, which they took care of early the following week.
开发人员仔细聆听了航运专家的发言,注意到“行程”的概念对他来说有多重要。确实,所有数据都已收集,行为隐含在行程报告中,但作为模型一部分的明确行程开辟了机会。
The developer had been listening closely enough to the shipping expert to notice how important the concept of an “itinerary” was to him. True, all the data was already being collected, and the behavior was implicit in the itinerary report, but the explicit Itinerary as part of the model opened up opportunities.
重构显式行程对象的好处:
Benefits of refactoring to the explicit Itinerary object:
1.更加清晰地定义路由服务的接口
1. Defining the interface of the Routing Service more expressively
2.将路线服务与预订数据库表分离
2. Decoupling the Routing Service from the booking database tables
3.明确预订应用程序和运营支持应用程序之间的关系(行程对象的共享)
3. Clarifying the relationship between the booking application and the operations support application (the sharing of the Itinerary object)
4.减少重复,因为行程单会导出预订报告和运营支持应用程序的装卸时间
4. Reducing duplication, because the Itinerary derives loading/unloading times for both the booking report and the operations support application
5.从预订报告中删除域逻辑并将其放置在隔离域层中
5. Removing domain logic from the booking report and placing it in the isolated domain layer
6.扩展通用语言,允许开发人员和领域专家之间以及开发人员之间更精确地讨论模型和设计
6. Expanding the UBIQUITOUS LANGUAGE, allowing a more precise discussion of the model and design between developers and domain experts and among the developers themselves
你需要的概念并不总是浮在表面,出现在对话或文件中。你可能需要挖掘和发明。挖掘的地方是你设计中最棘手的部分。程序正在做难以解释的复杂事情的地方。每个新要求似乎都会增加复杂性的地方。
The concept you need is not always floating on the surface, emerging in conversation or documents. You may have to dig and invent. The place to dig is the most awkward part of your design. The place where procedures are doing complicated things that are hard to explain. The place where every new requirement seems to add complexity.
有时甚至很难意识到缺少了一个概念。你可能有对象在做所有的工作,但发现有些责任的尴尬。或者,如果你确实意识到缺少了某些东西,你可能会找不到一个典型的解决方案。
Sometimes it can be hard to recognize that there even is a missing concept. You may have objects doing all the work but find some of the responsibilities awkward. Or, if you do realize something is missing, a model solution may elude you.
现在,您必须积极地让领域专家参与搜索。如果您很幸运,他们可能会喜欢尝试各种想法并试验该模型。如果您不那么幸运,您和您的开发人员同事将不得不提出这些想法,使用领域专家作为验证者,观察他或她脸上的不适或认可。
Now you have to actively engage the domain experts in the search. If you are lucky, they may enjoy playing with ideas and experimenting with the model. If you are not that lucky, you and your fellow developers will have to come up with the ideas, using the domain expert as a validator, watching for discomfort or recognition on his or her face.
下一个故事发生在一家投资商业贷款和其他生息资产的假设金融公司。一款跟踪这些投资及其收益的应用程序正在逐步发展,功能逐一完善。每天晚上,一个组件将作为批处理脚本运行,计算当天的所有利息和费用,然后将其正确记录在公司的会计软件中。
The next story is set in a hypothetical financial company that invests in commercial loans and other interest-bearing assets. An application that tracks those investments and the earnings from them has been evolving incrementally, feature by feature. Each night, one component was to run as a batch script, calculating all interest and fees for the day and then recording them appropriately in the company’s accounting software.
图 9.4. 一个尴尬的模型
Figure 9.4. An awkward model
夜间批处理脚本遍历每项资产,告知每项资产calculateInterestForDate()当天的日期。脚本获取返回值(收入金额)并将该金额连同特定分类账的名称一起传递给提供会计程序公共接口的服务。该软件将金额发布到指定的分类账。脚本经历了类似的过程,从每项资产中获取当天的费用,并将其发布到不同的分类账。
The nightly batch script iterated through each Asset, telling each to calculateInterestForDate() on that day’s date. The script took the return value (the amount earned) and passed this amount, along with the name of a specific ledger, to a SERVICE that provided the public interface of the accounting program. That software posted the amount to the named ledger. The script went through a similar process to get the day’s fees from each Asset, posting them to a different ledger.
一位开发人员一直在努力应对日益复杂的利息计算。她开始怀疑是否有更适合该任务的模型。这位开发人员请她最喜欢的领域专家帮助她深入研究问题领域。
A developer had been struggling with the increasing complexity of calculating interest. She started to suspect an opportunity for a model better suited to the task. This developer asked her favorite domain expert to help her dig into the problem area.
开发人员:我们的利息计算器已经失控了。
Developer: Our Interest Calculator is getting out of hand.
专家:这是个复杂的部分。我们还有更多的案件被搁置。
Expert: That is a complicated part. We still have more cases we’ve been holding back.
开发人员:我知道。我们可以通过替换不同的利息计算器来添加新的利息类型。但目前我们遇到的最大麻烦是所有这些特殊情况,即他们无法按时支付利息。
Developer: I know. We can add new interest types by substituting a different Interest Calculator. But what we’re having the most trouble with right now is all these special cases when they don’t pay the interest on schedule.
专家:这些其实都不是特殊情况。人们在付款时有很大的灵活性。
Expert: Those really aren’t special cases. There’s a lot of flexibility in when people pay.
开发人员:当我们从资产中分离出利息计算器时,它帮了大忙。我们可能需要进一步分解它。
Developer: Back when we factored out the Interest Calculator from the Asset, it helped a lot. We may need to break it up more.
专家:好的。
Expert: OK.
开发人员:我在想您可能有办法谈论这个利息计算。
Developer: I was thinking you might have a way of talking about this interest calculation.
专家:你的意思是什么?
Expert: What do you mean?
开发人员:嗯,例如,我们追踪会计期间内到期但未支付的利息。您对此有什么称呼吗?
Developer: Well, for example, we’re tracking the interest due but unpaid within an accounting period. Do you have a name for that?
专家:其实我们不是这么做的。利息收入和付款是分开记账的。
Expert: Well, we don’t really do it like that. The interest earned and the payment are quite separate postings.
开发人员:那么你不需要那个号码?
Developer: So you don’t need that number?
专家:嗯,有时我们可能会考虑这一点,但这不是我们做生意的方式。
Expert: Well, sometimes we might look at it, but it isn’t the way we do business.
开发人员:好的,如果付款和利息是分开的,也许我们应该这样建模。这看起来怎么样?[在白板上画草图]
Developer: OK, so if the payment and interest are separate, maybe we should model them that way. How does this look? [Sketching on whiteboard]
图 9.5
Figure 9.5
专家:我觉得这很有道理。但你只是把它从一个地方移到了另一个地方。
Expert: It makes sense, I guess. But you just moved it from one place to another.
开发人员:不过现在利息计算器只记录利息收入,而付款则单独记录该数字。这并没有简化很多,但它是否更好地反映了您的业务实践?
Developer: Except now the Interest Calculator only keeps track of interest earned, and the Payment keeps that number separately. It hasn’t simplified it a lot, but does it better reflect your business practice?
专家:啊,我明白了。我们也可以查看利息记录吗?就像付款记录一样。
Expert: Ah. I see. Could we have interest history, too? Like the Payment History.
开发人员:是的,这是一项新功能。但这可以添加到原始设计中。
Developer: Yes, that has been requested as a new feature. But that could have been added onto the original design.
专家:哦。好吧,当我看到利息和付款历史如此分开时,我以为你是在将利息拆分,以便更像付款历史那样组织它。你对权责发生制会计有所了解吗?
Expert: Oh. Well, when I saw interest and Payment History separated like that, I thought you were breaking up the interest to organize it more like the Payment History. Do you know anything about accrual basis accounting?
开发人员:请解释一下。
Developer: Please explain.
专家:每天,或者只要计划需要,我们就会将利息计入分类账。付款以不同的方式记账。你这里的总数有点尴尬。
Expert: Each day, or whenever the schedule calls for, we have an interest accrual that gets posted to a ledger. The payments are posted a different way. This aggregate you have here is a little awkward.
开发人员:您是说,如果我们保留一份“应计项目”清单,它们就可以根据需要进行汇总或……“发布”。
Developer: You’re saying that if we keep a list of “accruals,” they could be aggregated or . . . “posted” as needed.
专家:可能在应计日记账,但可以随时汇总。费用的计算方式相同,当然,记入不同的分类账。
Expert: Probably posted on the accrual date, but yes, aggregated anytime. Fees work the same way, posted to a different ledger, of course.
开发人员:实际上,如果只计算一天或一段时间的利息,那么利息计算会更简单。然后我们就可以把它们全部保留下来。这样怎么样?
Developer: Actually, the interest calculation would be simpler if it was done just for one day, or period. And then we could just hang on to them all. How about this?
图 9.6
Figure 9.6
专家:当然。看起来不错。我不知道为什么这对你来说会更容易。但基本上,任何资产的价值在于它可以产生的利息、费用等。
Expert: Sure. It looks good. I’m not sure why this would be easier for you. But basically, what makes any asset valuable is what it can accrue in interest, fees, and so on.
开发人员:您说费用以同样的方式收取?它们……是什么……记入不同的账本?
Developer: You said fees work the same way? They . . . what was it . . . post to different ledgers?
图 9.7
Figure 9.7
开发人员:通过此模型,我们可以计算利息,或者更确切地说,将利息计算器中的应计计算逻辑与跟踪分开。直到现在我才注意到费用计算器中有多少重复。此外,现在可以轻松添加不同类型的费用。
Developer: With this model, we get the interest calculation, or rather, the accrual calculation logic that was in the Interest Calculator separated from tracking. And I hadn’t noticed until now how much duplication there is in the Fee Calculator. Also, now the different kinds of fees can easily be added.
专家:是的,之前的计算是正确的,但是现在我明白了一切。
Expert: Yes, the calculation was correct before, but I can see everything now.
由于计算器类没有直接与设计的其他部分耦合,因此这是一个相当容易的重构。开发人员能够在几个小时内重写单元测试以使用新语言,并在第二天晚些时候让新设计投入使用。她最终做到了这一点。
Because the Calculator classes hadn’t been directly coupled with other parts of the design, this was a fairly easy refactoring. The developer was able to rewrite the unit tests to use the new language in a few hours and had the new design working late the next day. She ended up with this.
图 9.8. 重构后的更深层模型
Figure 9.8. A deeper model after refactoring
在重构的应用程序中,夜间批处理脚本会告诉每项资产。calculateAccrualsThroughDate()返回值是应计金额的集合,每个应计金额都会发布到指定的分类账中。
In the refactored application, the nightly batch script tells each Asset to calculateAccrualsThroughDate(). The return value is a collection of Accruals, each of whose amounts it posts to the indicated ledger.
新模式有几个优点。
The new model has several advantages. The change
1.使用“应计”一词丰富通用语言
1. Enriches the UBIQUITOUS LANGUAGE with the term “accrual”
2.将应计与付款脱钩
2. Decouples accrual from payment
3.将领域知识(例如发布到哪个分类账)从脚本移到领域层
3. Moves domain knowledge (such as which ledger to post to) from the script and into the domain layer
4.以适合业务的方式将费用和利息结合在一起,并消除代码中的重复
4. Brings fees and interest together in a way that fits the business and eliminates duplication in the code
5. 提供一条直接的途径,以应计计划的形式添加新的费用和利息变化
5. Provides a straightforward path for adding new variations of fees and interest as Accrual Schedules
这次,开发人员必须挖掘她需要的新概念。她看到了利息计算的尴尬,并努力寻找更深层次的答案。
This time, the developer had to dig for the new concepts she needed. She could see the awkwardness of the interest calculations and made a committed effort to look for a deeper answer.
她很幸运能找到一位聪明且积极主动的银行专家作为合作伙伴。如果专业技能来源比较被动,她可能会犯更多错误,并更多地依赖其他开发人员作为头脑风暴伙伴。进展可能会比较慢,但仍然有可能。
She was lucky to have an intelligent and motivated partner in the banking expert. With a more passive source of expertise, she would have made more false starts and depended more on other developers as brainstorming partners. Progress would have been slower, but still possible.
不同领域的专家根据自己的经验和需求以不同的方式看待事物。即使是同一个人,经过仔细分析后,提供的信息在逻辑上也不一致。我们在深入研究程序需求时经常遇到这种令人讨厌的矛盾,它们可能是更深层次模型的重要线索。有些只是术语的变化,或者是基于误解。但有一种情况是,专家的两个事实陈述似乎相互矛盾。
Different domain experts see things different ways based on their experience and needs. Even the same person provides information that is logically inconsistent after careful analysis. Such pesky contradictions, which we encounter all the time when digging into program requirements, can be great clues to deeper models. Some are just variations in terminology or are based on misunderstanding. But there is a residue where two factual statements by experts seem to contradict.
天文学家伽利略曾经提出过一个悖论。感官证据清楚地表明地球是静止的:人类并没有被吹走并落后。然而,哥白尼提出了一个令人信服的论点,即地球正以相当快的速度绕太阳旋转。调和这一悖论可能会揭示大自然运作的一些深刻原理。
The astronomer Galileo once posed a paradox. The evidence of the senses clearly indicates that the Earth is stationary: people are not being blown off and falling behind. Yet Copernicus had made a compelling argument that the Earth was moving around the sun quite rapidly. Reconciling this might reveal something profound about how nature works.
伽利略设计了一个思维实验。如果骑手从奔跑的马上扔下一个球,它会落在哪里?当然,球会跟着马一起移动,直到它落在马蹄边的地面上,就像马静止不动一样。由此,他推导出惯性参考系概念的早期形式,解决了悖论,并产生了一个更有用的运动物理学模型。
Galileo devised a thought experiment. If a rider dropped a ball from a running horse, where would it fall? Of course, the ball would move along with the horse until it hit the ground by the horse’s feet, just as if the horse were standing still. From this he deduced an early form of the idea of inertial frames of reference, solving the paradox and leading to a much more useful model of the physics of motion.
好吧。我们的矛盾通常不那么有趣,含义也不那么深刻。即便如此,同样的思维模式往往有助于洞察问题领域的表层,获得更深刻的见解。
OK. Our contradictions are usually not so interesting, nor the implications so profound. Even so, this same pattern of thought often helps pierce the superficial layers of a problem domain into a deeper insight.
调和所有矛盾是不切实际的,甚至可能不是理想的做法。(第 14 章深入探讨了如何做出决定以及如何处理结果。)但是,即使存在矛盾,思考两个陈述如何都适用于同一个外部现实也是很有启发的。
It is not practical to reconcile all contradictions, and it may not even be desirable. (Chapter 14 delves into how to decide and how to manage the result.) However, even when a contradiction is left in place, contemplation of how two statements could both apply to the same external reality can be revealing.
在寻找模型概念时,不要忽视显而易见的东西。在许多领域,你可以找到解释基本概念和传统智慧的书籍。你仍然需要与你自己的领域专家合作,提炼与你的问题相关的部分,并将其分解成适合面向对象软件的东西。但你可以从一个连贯的、经过深思熟虑的观点开始。
Don’t overlook the obvious when seeking model concepts. In many fields, you can find books that explain the fundamental concepts and conventional wisdom. You still have to work with your own domain experts to distill the part relevant to your problem and to crunch it into something suited to object-oriented software. But you may be able to start with a coherent, deeply considered view.
让我们想象一下上例中讨论的投资跟踪应用程序的另一个场景。和以前一样,故事从开发人员意识到设计变得笨重开始,尤其是利息计算器。但在这种情况下,领域专家的主要职责在于其他方面,他对帮助软件开发项目没有多大兴趣。在这种情况下,开发人员无法向专家寻求头脑风暴会议,以探究她怀疑隐藏在表面之下的缺失概念。
Let’s imagine a different scenario for the investment-tracking application discussed in the previous example. Just as before, the story starts with the developer realizing that the design is getting unwieldy, particularly the Interest Calculator. But in this scenario, the domain expert’s primary responsibilities lie elsewhere, and he doesn’t have much interest in helping the software development project. In this scenario, the developer couldn’t turn to the expert for a brainstorming session to probe for the missing concepts she suspected to be lurking under the surface.
于是,她去了书店。稍微浏览了一下,她找到了一本喜欢的会计入门书,然后浏览了一下。她发现了一整套定义明确的概念。其中一段摘录特别激发了她的思考:
Instead, she went to the bookstore. After a little browsing, she found an introductory accounting book she liked, and she skimmed it. She discovered a whole system of well-defined concepts. An excerpt that particularly fired her thinking:
权责发生制会计。此方法在收入产生时确认收入,即使尚未支付。所有费用也显示其发生时间,无论是否已支付或已记入账单,稍后再支付。任何到期债务(包括税款)都将显示为费用。
Accrual Basis Accounting. This method recognizes income when it is earned, even if it is not paid. All expenses also show when they are incurred whether they have been paid for or billed to be paid at a later date. Any obligation due, including taxes, will be shown as expense.
—财务与会计:如何在没有 MBA、CPA 或博士学位的情况下记账和管理财务,作者:苏珊娜·卡普兰 (Adams Media, 2000)
—Finance and Accounting: How to Keep Your Books and Manage Your Finances Without an MBA, a CPA or a Ph.D., by Suzanne Caplan (Adams Media, 2000)
这位开发人员不再需要重新设计会计。经过与另一位开发人员的头脑风暴,她想出了一个模型。
The developer no longer needed to reinvent accounting. After some brainstorming with another developer, she came up with a model.
图 9.9. 基于书本学习的更深层次模型
Figure 9.9. A somewhat deeper model based on book learning
她没有意识到资产是收入来源,所以计算器仍然存在。账本的知识仍然在应用程序中,而不是它可能属于的领域层。但她确实将支付问题与收入的应计问题分开,这是最成问题的领域,她将“应计”一词引入了模型和 UBIQUITOUS LANGUAGE 中。后续迭代可能会进一步完善。
She did not have the insight that Assets are income generators, and so the Calculators are still there. The knowledge of ledgers is still in the application, rather than the domain layer where it probably belongs. But she did separate the issue of payment from the accrual of income, which was the most problematic area, and she introduced the word “accrual” into the model and into the UBIQUITOUS LANGUAGE. Further refinement could come with later iterations.
当她终于有机会与领域专家交谈时,他非常惊讶。这是程序员第一次对他的工作表现出一丝兴趣。由于职责分配的方式,专家从未与她交谈,坐下来仔细研究模型,就像上一个场景中发生的那样。然而,由于这位开发人员的知识使她能够提出更好的问题,从那时起,专家确实认真听取了她的意见,并特别努力及时回答她的问题。
When she did finally have the chance to talk with the domain expert, he was quite surprised. It was the first time a programmer had shown a glimmer of interest in what he did. Due to the way his responsibilities were assigned, the expert never engaged with her, sitting down to go over the model, as happened in the previous scenario. However, because this developer’s knowledge allowed her to ask better questions, from then on the expert did listen to her carefully, and he made a special effort to answer her questions promptly.
当然,这不是非此即彼的命题。即使有领域专家的充分支持,阅读文献以掌握该领域的理论也是有益的。大多数企业没有精炼到会计或财务水平的模型,但在许多企业中,该领域的思想家已经组织和抽象了企业的常见做法。
Of course, this is not an either-or proposition. Even with ample support from domain experts, it pays to look at the literature to get a grasp of the theory of the field. Most businesses do not have models refined to the level of accounting or finance, but in many there have been thinkers in the field who have organized and abstracted the common practices of the business.
开发人员还有另一个选择,那就是阅读由具有开发经验的其他软件专业人员编写的内容在这一领域。例如, 《分析模式:可重用对象模型》(Fowler 1997 )一书的第 6 章可能会让她走上完全不同的方向,不一定更好或更坏。这样的阅读不会提供现成的解决方案。它会为她自己的实验提供几个新的起点,以及那些涉足该领域的人的精炼经验。她就不必再重新发明轮子了。第 11 章“应用分析模式”将进一步探讨此选项。
Yet another option the developer had was to read something written by another software professional with development experience in this domain. For example, Chapter 6 of the book Analysis Patterns: Reusable Object Models (Fowler 1997) would have sent her in quite a different direction, not necessarily better or worse. Such reading would not have provided an off-the-shelf solution. It would have given several new starting points for her own experiments, along with the distilled experience of people who have traveled the territory. She would have been spared reinventing the wheel. Chapter 11, “Applying Analysis Patterns,” will delve further into this option.
我给出的例子并没有传达出所涉及的反复试验的数量。我可能会在谈话中跟踪六条线索,然后才找到一条看起来足够清晰和有用的线索,可以在模型中尝试。我最终会至少替换一次那个,因为额外的经验和知识积累会带来更好的想法。建模者/设计师不能执着于自己的想法。
The examples I’ve given don’t convey the amount of trial and error involved. I might follow half a dozen leads in conversation before finding one that seems clear and useful enough to try out in the model. I’ll end up replacing that one at least once later, as additional experience and knowledge crunching serve up better ideas. A modeler/designer cannot afford to get attached to his own ideas.
所有这些方向的改变都不仅仅是一次挫折。每次改变都会对模型产生更深刻的洞察。每次重构都会让设计更加灵活,下次更容易改变,随时准备在需要弯曲的地方弯曲。
All these changes of direction are not just thrashing. Each change embeds deeper insight into the model. Each refactoring leaves the design more supple, easier to change the next time, ready to bend in the places that turn out to need to bend.
无论如何,真的没有选择。实验是了解什么可行、什么不可行的方法。试图避免设计中的失误会导致质量较低的结果,因为它将基于较少的经验。而且它很容易比一系列快速实验花费更长的时间。
There really is no choice, anyway. Experimentation is the way to learn what works and doesn’t. Trying to avoid missteps in design will result in a lower quality result because it will be based on less experience. And it can easily take longer than a series of quick experiments.
面向对象范式引导我们寻找和发明某些类型的概念。事物,甚至是“应计项目”等非常抽象的事物,以及这些事物采取的行动,都是大多数对象模型的核心。这些是面向对象设计入门书籍中讨论的“名词和动词”。但其他重要的概念类别也可以在模型中明确表达。
The object-oriented paradigm leads us to look for and invent certain kinds of concepts. Things, even very abstract ones such as “accruals,” are the meat of most object models, along with the actions those things take. These are the “nouns and verbs” that introductory object-oriented design books talk about. But other important categories of concepts can be made explicit in a model as well.
我将讨论三种这样的类别,当我开始接触对象时,这些类别对我来说并不明显。随着我了解这些类别,我的设计变得更加清晰。
I’ll discuss three such categories that were not obvious to me when I started with objects. My designs became sharper with each one of these I learned.
约束是模型概念中特别重要的一类。它们通常隐含地出现,而明确地表达它们可以极大地改进设计。
Constraints make up a particularly important category of model concepts. They often emerge implicitly, and expressing them explicitly can greatly improve a design.
有时约束在对象或方法中很常见。“Bucket”对象必须保证其容量不超过其容量的不变性。
Sometimes constraints find a natural home in an object or method. A “Bucket” object must guarantee the invariant that it does not hold more than its capacity.
图 9.10
Figure 9.10
可以在每个能够改变内容的操作中使用案例逻辑来强制执行这样的简单不变量。
A simple invariant like this can be enforced using case logic in each operation capable of changing contents.
类 Bucket {
私有浮点容量;
私有浮点内容;
公共无效pourIn(浮点添加体积){
如果(内容+添加体积>容量){
内容=容量;
}否则{
内容=内容+添加体积;
}
}
}
class Bucket {
private float capacity;
private float contents;
public void pourIn(float addedVolume) {
if (contents + addedVolume > capacity) {
contents = capacity;
} else {
contents = contents + addedVolume;
}
}
}
这个逻辑非常简单,规则显而易见。但是您可以很容易地想象这个约束在更复杂的类中会丢失。让我们将其分解为一个单独的方法,并使用一个清晰明确地表达约束重要性的名称。
This logic is so simple that the rule is obvious. But you can easily imagine this constraint getting lost in a more complicated class. Let’s factor it into a separate method, with a name that clearly and explicitly expresses the significance of the constraint.
类 Bucket {
私有浮点容量;
私有浮点内容;
公共无效 pourIn(浮点添加的体积){
浮点体积存在 = 内容 + 添加的体积;
内容 = constrainedToCapacity(体积存在);
}
私有浮点 constrainedToCapacity(浮点体积放置){
如果(体积放置 > 容量)返回容量;
返回体积放置;
}
}
class Bucket {
private float capacity;
private float contents;
public void pourIn(float addedVolume) {
float volumePresent = contents + addedVolume;
contents = constrainedToCapacity(volumePresent);
}
private float constrainedToCapacity(float volumePlacedIn) {
if (volumePlacedIn > capacity) return capacity;
return volumePlacedIn;
}
}
这两个版本的代码都强制执行了约束,但第二个版本与模型的关系更明显(模型驱动设计的基本要求)。这个非常简单的规则在其原始形式下是可以理解的,但是当强制执行的规则更复杂时,它们就会开始压倒它们所适用的对象或操作,就像任何隐式概念一样。将约束分解到其自己的方法中使我们能够为其赋予一个揭示意图的名称,从而使约束在我们的设计中变得明确。它现在是一个我们可以讨论的命名事物。这种方法也为约束提供了空间。比这更复杂的规则很容易产生比其调用者(pourIn()在本例中为方法)更长的方法。这样,调用者可以保持简单并专注于其任务,而约束可以在必要时增加复杂性。
Both versions of this code enforce the constraint, but the second has a more obvious relationship to the model (the basic requirement of MODEL-DRIVEN DESIGN). This very simple rule was understandable in its original form, but when the rules being enforced are more complex, they start to overwhelm the object or operation they apply to, as any implicit concept does. Factoring the constraint into its own method allows us to give it an intention-revealing name that makes the constraint explicit in our design. It is now a named thing we can discuss. This approach also gives the constraint room. A more complex rule than this might easily produce a method longer than its caller (the pourIn() method, in this case). This way, the caller stays simple and focused on its task while the constraint can grow in complexity if need be.
这种单独的方法为约束提供了一些发展空间,但很多情况下约束无法轻松地融入单个方法中。或者即使方法保持简单,它也可能调用对象不需要的信息来履行其主要职责。规则可能在现有对象中没有合适的位置。
This separate method gives the constraint some room to grow, but there are lots of cases when a constraint just can’t fit comfortably in a single method. Or even if the method stays simple, it may call on information that the object doesn’t need for its primary responsibility. The rule may just have no good home in an existing object.
以下是一些警告信号,表明约束正在扭曲其宿主对象的设计。
Here are some warning signs that a constraint is distorting the design of its host object.
1.评估约束需要不符合对象定义的数据。
1. Evaluating a constraint requires data that does not otherwise fit the object’s definition.
2.相关规则出现在多个对象中,强制在不属于同一家族的对象之间进行重复或继承。
2. Related rules appear in multiple objects, forcing duplication or inheritance between objects that are not otherwise a family.
3.许多设计和需求讨论都围绕着约束,但在实施过程中,它们却隐藏在程序代码中。
3. A lot of design and requirements conversation revolves around the constraints, but in the implementation, they are hidden away in procedural code.
当约束掩盖了对象的基本职责,或者约束在领域中很突出但在模型中并不突出时,您可以将其分解为显式对象,甚至将其建模为一组对象和关系。(可以在“对象约束语言:使用 UML 进行精确建模” [ Warmer 和 Kleppe 1999 ] 中找到对此主题的深入、半正式处理。)
When the constraints are obscuring the object’s basic responsibility, or when the constraint is prominent in the domain yet not prominent in the model, you can factor it out into an explicit object or even model it as a set of objects and relationships. (One in-depth, semiformal treatment of this subject can be found in The Object Constraint Language: Precise Modeling with UML [Warmer and Kleppe 1999].)
在第 1 章中,我们研究了一种常见的航运业务惯例:预订的货物数量超过运输能力的 10%。(经验告诉航运公司,这种超额预订可以弥补最后一刻的取消,因此他们的船只几乎满载航行。)
In Chapter 1, we worked with a common shipping business practice: booking 10 percent more cargo than the transports could handle. (Experience has taught shipping firms that this overbooking compensates for last-minute cancellations, so their ships will sail nearly full.)
通过添加一个表示约束的新类,在图表和代码中明确了Voyage和Cargo之间的关联的约束。
This constraint on the association between Voyage and Cargo was made explicit, both in the diagrams and in the code, by adding a new class that represented the constraint.
图 9.11. 重构模型以明确政策
Figure 9.11. The model refactored to make policy explicit
要查看完整示例中的代码和推理,请参见第17页。
To review the code and reasoning in the full example, see page 17.
首先,我们同意我们不想让程序成为我们模型的一个突出方面。对象旨在封装程序,让我们思考它们的目标或意图。
Right up front, let’s agree that we do not want to make procedures a prominent aspect of our model. Objects are meant to encapsulate the procedures and let us think about their goals or intentions instead.
我在这里谈论的是领域中存在的流程,我们必须在模型中表示这些流程。当这些流程出现时,它们往往会造成尴尬的对象设计。
What I am talking about here are processes that exist in the domain, which we have to represent in the model. When these emerge, they tend to make for awkward object designs.
本章中的第一个示例描述了一个安排货物运输路线的运输系统。此路线安排过程具有业务意义。服务是明确表达此类过程的一种方式,同时仍封装极其复杂的算法。
The first example in this chapter described a shipping system that routed cargo. This routing process was something with business meaning. A SERVICE is one way of expressing such a process explicitly, while still encapsulating the extremely complex algorithms.
当有多种方法可以执行某个过程时,另一种方法是将算法本身或其某个关键部分作为对象。在过程之间进行选择就变成了在这些对象之间进行选择,每个对象都代表一种不同的策略。(第 12 章将更详细地介绍策略在领域中的使用。)
When there is more than one way to carry out a process, another approach is to make the algorithm itself, or some key part of it, an object in its own right. The choice between processes becomes a choice between these objects, each of which represents a different STRATEGY. (Chapter 12 will look in more detail at the use of STRATEGIES in the domain.)
区分应该明确的过程和应该隐藏的过程的关键很简单:这是领域专家谈论的内容,还是计算机程序机制的一部分?
The key to distinguishing a process that ought to be made explicit from one that should be hidden is simple: Is this something the domain experts talk about, or is it just part of the mechanism of the computer program?
约束和过程是两大类模型概念,在使用面向对象语言进行编程时不会立即想到它们,但一旦我们开始将它们视为模型元素,它们确实可以使设计更加清晰。
Constraints and processes are two broad categories of model concepts that don’t come leaping to mind when programming in an object-oriented language, yet they can really sharpen up a design once we start thinking about them as model elements.
一些有用的概念类别要窄得多。我将用一个更具体但相当常见的概念来结束本章。S PECIFICATION提供了一种简洁的方式来表达某些类型的规则,将它们从条件逻辑中解脱出来,并在模型中明确它们。
Some useful categories of concepts are much narrower. I’ll round out this chapter with one much more specific, yet quite common. SPECIFICATION provides a concise way of expressing certain kinds of rules, extricating them from conditional logic and making them explicit in the model.
我与 Martin Fowler ( Evans and Fowler 1997 )合作开发了SPECIFICATION。概念的简单性掩盖了应用和实施的微妙性,因此本节将详细介绍。第 10 章将对此模式进行扩展,并进行更多讨论。在阅读完后面对该模式的初步解释后,您可能希望略读“应用和实施 S PECIFICATIONS ”部分,直到您真正尝试应用该模式。
I developed SPECIFICATION in collaboration with Martin Fowler (Evans and Fowler 1997). The simplicity of the concept belies the subtlety in application and implementation, so there is a lot of detail in this section. There will be even more discussion in Chapter 10, where the pattern is extended. After reading the initial explanation of the pattern that follows, you may want to skim the “Applying and Implementing SPECIFICATIONS” section, until you are actually attempting to apply the pattern.
在各种应用中,布尔测试方法实际上是小规则的一部分。只要它们很简单,我们就用测试方法处理它们,例如anIterator.hasNext()或anInvoice.isOverdue()。在Invoice类中, 中的代码isOverdue()是评估规则的算法。例如,
In all kinds of applications, Boolean test methods appear that are really parts of little rules. As long as they are simple, we handle them with testing methods, such as anIterator.hasNext() or anInvoice.isOverdue(). In an Invoice class, the code in isOverdue() is an algorithm that evaluates a rule. For example,
公共布尔值isOverdue(){
Date currentDate = new Date();
返回currentDate.after(dueDate);
}
public boolean isOverdue() {
Date currentDate = new Date();
return currentDate.after(dueDate);
}
但并非所有规则都如此简单。在同一个发票类上,另一条规则anInvoice.isDelinquent()可能首先测试发票是否逾期,但这只是开始。宽限期政策可能取决于客户帐户的状态。一些逾期发票将准备好第二次通知,而其他发票将准备好发送给收款机构。客户的付款历史、公司对不同产品线的政策……发票作为付款请求的清晰度很快就会在大量的规则评估代码中消失。发票还将对不支持该基本含义的域类和子系统产生各种依赖关系。
But not all rules are so simple. On the same Invoice class, another rule, anInvoice.isDelinquent() would presumably start with testing if the Invoice is overdue, but that would just be the beginning. A policy on grace periods could depend on the status of the customer’s account. Some delinquent invoices will be ready for a second notice, while others will be ready to be sent to a collection agency. The payment history of the customer, company policy on different product lines . . . the clarity of Invoice as a request for payment will soon be lost in the sheer mass of rule evaluation code. The Invoice will also develop all sorts of dependencies on domain classes and subsystems that do not support that basic meaning.
此时,为了挽救Invoice类,开发人员通常会将规则评估代码重构到应用程序层(在本例中为账单收集应用程序)。现在规则已经与域层完全分离,留下了一个死数据对象,它不表达业务模型固有的规则。这些规则需要保留在域层中,但它们不适合被评估的对象(在本例中为Invoice)。不仅如此,评估方法还充斥着条件代码,这使得规则难以阅读。
At this point, in an attempt to save the Invoice class, a developer will often refactor the rule evaluation code into the application layer (in this case, a bill collection application). Now the rules have been separated from the domain layer altogether, leaving behind a dead data object that does not express the rules inherent in the business model. These rules need to stay in the domain layer, but they don’t fit into the object being evaluated (the Invoice in this case). Not only that, but evaluating methods swell with conditional code, which make the rule hard to read.
使用逻辑编程范式的开发人员会以不同的方式处理这种情况。此类规则将表示为谓词。谓词是求值为“真”或“假”的函数并且可以使用“AND”和“OR”等运算符组合起来以表达更复杂的规则。使用谓词,我们可以明确声明规则并将其与 Invoice 一起使用。如果我们只处于逻辑范式中就好了。
Developers working in the logic-programming paradigm would handle this situation differently. Such rules would be expressed as predicates. Predicates are functions that evaluate to “true” or “false” and can be combined using operators such as “AND” and “OR” to express more complex rules. With predicates, we could declare rules explicitly and use them with the Invoice. If only we were in the logic paradigm.
看到这一点,人们开始尝试用对象来实现逻辑规则。有些尝试非常复杂,有些则很幼稚。有些雄心勃勃,有些则比较谦虚。有些结果很有价值,有些则作为失败的实验被抛弃。少数尝试导致项目失败。有一点很清楚:尽管这个想法很有吸引力,但完全用对象来实现逻辑是一项艰巨的任务。(毕竟,逻辑编程本身就是一个完整的建模和设计范例。)
Seeing this, people have made attempts at implementing logical rules in terms of objects. Some such attempts were very sophisticated, others naive. Some were ambitious, others modest. Some turned out valuable, some were tossed aside as failed experiments. A few attempts were allowed to derail their projects. One thing is clear: As appealing as the idea is, full implementation of logic in objects is a major undertaking. (After all, logic programming is a whole modeling and design paradigm in its own right.)
业务规则通常不适合任何明显的实体或值对象的责任,并且它们的多样性和组合可能会压倒领域对象的基本含义。但是将规则移出领域层甚至更糟,因为领域代码不再表达模型。
Business rules often do not fit the responsibility of any of the obvious ENTITIES or VALUE OBJECTS, and their variety and combinations can overwhelm the basic meaning of the domain object. But moving the rules out of the domain layer is even worse, since the domain code no longer expresses the model.
逻辑编程提供了独立、可组合的规则对象概念,称为“谓词”,但用对象全面实现这一概念非常麻烦。它太过通用,无法像更专业的设计那样传达意图。
Logic programming provides the concept of separate, combinable, rule objects called “predicates,” but full implementation of this concept with objects is cumbersome. It is also so general that it doesn’t communicate intent as much as more specialized designs.
幸运的是,我们实际上并不需要完全实现逻辑编程才能获得巨大的好处。我们的大多数规则都属于一些特殊情况。我们可以借用谓词的概念,并创建求值为布尔值的专用对象。那些失控的测试方法将巧妙地扩展为它们自己的对象。它们是可以分解为单独的VALUE OBJECT的小真值测试。这个新对象可以评估另一个对象,以查看谓词对该对象是否为真。
Fortunately, we don’t really need to fully implement logic programming to get a large benefit. Most of our rules fall into a few special cases. We can borrow the concept of predicates and create specialized objects that evaluate to a Boolean. Those testing methods that get out of hand will neatly expand into objects of their own. They are little truth tests that can be factored out into a separate VALUE OBJECT. This new object can evaluate another object to see if the predicate is true for that object.
图 9.12
Figure 9.12
换句话说,新对象是一种规范。SPECIFICATION规定了对另一个对象状态的约束,该约束可能存在也可能不存在。它有多种用途,但传达最基本概念的一种是,SPECIFICATION可以测试任何对象以查看其是否满足指定的标准。
To put it another way, the new object is a specification. A SPECIFICATION states a constraint on the state of another object, which may or may not be present. It has multiple uses, but one that conveys the most basic concept is that a SPECIFICATION can test any object to see if it satisfies the specified criteria.
所以:
Therefore:
为特殊目的创建显式谓词类VALUE OBJECTS。SPECIFICATION是确定对象是否满足某些条件的谓词。
Create explicit predicate-like VALUE OBJECTS for specialized purposes. A SPECIFICATION is a predicate that determines if an object does or does not satisfy some criteria.
许多SPECIFICATION都是简单的专用测试,例如逾期发票示例。在规则复杂的情况下,可以扩展概念以允许组合简单的规范,就像谓词与逻辑运算符组合一样。(下一章将讨论此技术。)基本模式保持不变,并提供从简单模型到复杂模型的路径。
Many SPECIFICATIONS are simple, special-purpose tests, as in the delinquent invoice example. In cases where the rules are complex, the concept can be extended to allow simple specifications to be combined, just as predicates are combined with logical operators. (This technique will be discussed in the next chapter.) The fundamental pattern stays the same and provides a path from the simpler to more complex models.
可以使用规范来建模逾期发票的情况,该规范说明了逾期的含义,并可以评估任何发票并做出判定。
The case of the delinquent invoice can be modeled using a SPECIFICATION that states what it means to be delinquent and that can evaluate any Invoice and make the determination.
图 9.13。更详细的违约规则被分解为规范
Figure 9.13. A more elaborate delinquency rule factored out as a SPECIFICATION
SPECIFICATION将规则保存在领域层。因为规则是一个成熟的对象,所以设计可以更明确地反映模型。FACTORY可以使用来自其他来源(例如客户帐户或公司政策数据库)的信息来配置 SPECIFICATION。从 Invoice 直接访问这些来源会将对象耦合在一起,而这与付款请求( Invoice的基本职责)无关。在本例中,需要创建逾期发票规范,用它来评估某些发票,然后将其丢弃,因此需要内置一个特定的评估日期 — — 这是一个很好的简化。SPECIFICATION可以简单、直接地获得完成其工作所需的信息。
The SPECIFICATION keeps the rule in the domain layer. Because the rule is a full-fledged object, the design can be a more explicit reflection of the model. A FACTORY can configure a SPECIFICATION using information from other sources, such as the customer’s account or the corporate policy database. Providing direct access to these sources from the Invoice would couple the objects in a way that does not relate to the request for payment (the basic responsibility of Invoice). In this case, the Delinquent Invoice Specification was to be created, used to evaluate some Invoices, and then discarded, so a specific evaluation date was built right in—a nice simplification. A SPECIFICATION can be given the information it will need to do its job in a simple, straightforward way.
SPECIFICATION的基本概念非常简单,有助于我们思考领域建模问题。但模型驱动设计需要有效的实现,同时也要表达这一概念。要做到这一点,需要更深入地研究如何应用该模式。领域模式不仅仅是 UML 图的一个巧妙想法;它是一种保留模型驱动设计的编程问题解决方案。
The basic concept of SPECIFICATION is very simple and helps us think about a domain modeling problem. But a MODEL-DRIVEN DESIGN requires an effective implementation that also expresses the concept. To pull that off requires digging a little deeper into how the pattern will be applied. A domain pattern is not just a neat idea for a UML diagram; it is a solution to a programming problem that retains a MODEL-DRIVEN DESIGN.
当您适当地应用模式时,您可以利用关于如何处理一类领域建模问题的整个思想体系,并且可以从寻找有效实现的多年经验中受益。后面的SPECIFICATION讨论中有很多细节:许多功能选项和实现方法。模式不是一本菜谱。它让您从经验基础开始开发解决方案,并为您提供一些语言来谈论您正在做的事情。
When you apply a pattern appropriately, you can tap into a whole body of thought about how to approach a class of domain modeling problem, and you can benefit from years of experience in finding effective implementations. There is a lot of detail in the discussion of SPECIFICATION that follows: many options for features and approaches to implementation. A pattern is not a cookbook. It lets you start from a base of experience to develop your solution, and it gives you some language to talk about what you are doing.
第一次阅读时,您可能希望浏览一下关键概念。稍后,当您遇到这种情况时,您可以回过头来借鉴详细讨论中的经验。然后,您就可以找到解决问题的方法。
You may want to skim the key concepts when first reading. Later, when you run into the situation, you can come back and draw on the experience captured in the detailed discussion. Then you can go and figure out a solution to your problem.
SPECIFICATION的大部分价值在于它统一了看似完全不同的应用程序功能。我们可能需要为这三个目的中的一个或多个指定对象的状态。
Much of the value of SPECIFICATION is that it unifies application functionality that may seem quite different. We might need to specify the state of an object for one or more of these three purposes.
1.验证一个对象是否满足某些需求或可用于某些目的
1. To validate an object to see if it fulfills some need or is ready for some purpose
2.从集合中选择一个对象(例如查询逾期发票的情况)
2. To select an object from a collection (as in the case of querying for overdue invoices)
3.指定创建新对象以满足某些需要
3. To specify the creation of a new object to fit some need
这三种用途(验证、选择和按订单生产)在概念层面上是相同的。如果没有诸如SPECIFICATION这样的模式,相同的规则可能会以不同的形式出现,甚至可能以相互矛盾的形式出现。概念上的统一性可能会丧失。应用SPECIFICATION模式允许使用一致的模型,即使实现可能必须有所不同。
These three uses—validation, selection, and building to order—are the same on a conceptual level. Without a pattern such as SPECIFICATION, the same rule may show up in different guises, and possibly contradictory forms. The conceptual unity can be lost. Applying the SPECIFICATION pattern allows a consistent model to be used, even when the implementation may have to diverge.
SPECIFICATION最简单的用途是验证,并且它是最直接地展示概念的用途。
The simplest use of a SPECIFICATION is validation, and it is the use that demonstrates the concept most straightforwardly.
图 9.14. 应用SPECIFICATION进行验证的模型
Figure 9.14. A model applying a SPECIFICATION for validation
class DelinquentInvoiceSpecification extends
InvoiceSpecification {
private Date currentDate;
// 实例在单个日期使用和丢弃
public DelinquentInvoiceSpecification(Date currentDate) {
this.currentDate = currentDate;
}
public boolean isSatisfiedBy(Invoice candidates) {
int gracePeriod =
candidates.customer().getPaymentGracePeriod();
Date firmDeadline =
DateUtility.addDaysToDate(candidate.dueDate(),
gracePeriod);
return currentDate.after(firmDeadline);
}
}
class DelinquentInvoiceSpecification extends
InvoiceSpecification {
private Date currentDate;
// An instance is used and discarded on a single date
public DelinquentInvoiceSpecification(Date currentDate) {
this.currentDate = currentDate;
}
public boolean isSatisfiedBy(Invoice candidate) {
int gracePeriod =
candidate.customer().getPaymentGracePeriod();
Date firmDeadline =
DateUtility.addDaysToDate(candidate.dueDate(),
gracePeriod);
return currentDate.after(firmDeadline);
}
}
现在,假设我们需要在销售人员介绍拖欠账单的客户时显示红旗。我们只需在客户端类中编写一个方法,如下所示。
Now, suppose we need to display a red flag whenever a salesperson brings up a customer with delinquent bills. We just have to write a method in a client class, something like this.
public boolean accountIsDelinquent(Customer customer) {
日期 today = new Date();
规范 delinquentSpec =
new DelinquentInvoiceSpecification(today);
迭代器 it = customer.getInvoices().iterator();
while (it.hasNext()) {
发票候选人 = (发票) it.next();
如果 (delinquentSpec.isSatisfiedBy(candidate)) 返回 true;
}
返回 false;
}
public boolean accountIsDelinquent(Customer customer) {
Date today = new Date();
Specification delinquentSpec =
new DelinquentInvoiceSpecification(today);
Iterator it = customer.getInvoices().iterator();
while (it.hasNext()) {
Invoice candidate = (Invoice) it.next();
if (delinquentSpec.isSatisfiedBy(candidate)) return true;
}
return false;
}
验证测试单个对象以查看其是否符合某些标准,大概是为了让客户端能够根据结论采取行动。另一个常见需求是根据某些标准选择对象集合的子集。此处可以应用相同的SPECIFICATION概念,但实施问题有所不同。
Validation tests an individual object to see if it meets some criteria, presumably so that the client can act on the conclusion. Another common need is to select a subset of a collection of objects based on some criteria. The same concept of SPECIFICATION can be applied here, but implementation issues are different.
假设有一个应用程序要求列出所有拖欠发票的客户。理论上,我们之前定义的拖欠发票规范仍然有效,但在实践中,其实现可能必须改变。为了证明概念是相同的,我们首先假设发票数量很少,可能已经在内存中了。在这种情况下,为验证而开发的简单实现仍然有效。发票存储库可以有一个通用方法,根据规范选择发票:
Suppose there was an application requirement to list all customers with delinquent Invoices. In theory, the Delinquent Invoice Specification that we defined before will still serve, but in practice its implementation would probably have to change. To demonstrate that the concept is the same, let’s assume first that the number of Invoices is small, maybe already in memory. In this case, the straightforward implementation developed for validation still serves. The Invoice Repository could have a generalized method to select Invoices based on a SPECIFICATION:
公共集合 selectSatisfying(InvoiceSpecification spec) {
设置 results = new HashSet();
迭代器 it = invoices.iterator();
while (it.hasNext()) {
发票候选 = (Invoice) it.next();
如果 (spec.isSatisfiedBy(candidate)) results.add(candidate);
}
返回结果;
}
public Set selectSatisfying(InvoiceSpecification spec) {
Set results = new HashSet();
Iterator it = invoices.iterator();
while (it.hasNext()) {
Invoice candidate = (Invoice) it.next();
if (spec.isSatisfiedBy(candidate)) results.add(candidate);
}
return results;
}
因此,客户可以使用单个代码语句获取所有逾期发票的集合:
So a client could obtain a collection of all delinquent Invoices with a single code statement:
设置 delinquentInvoices = invoiceRepository.selectSatisfying(
new DelinquentInvoiceSpecification(currentDate));
Set delinquentInvoices = invoiceRepository.selectSatisfying(
new DelinquentInvoiceSpecification(currentDate));
这行代码确立了操作背后的概念。当然,Invoice对象可能不在内存中。可能有数千个。在典型的业务系统中,数据可能位于关系数据库中。并且,正如前面章节所指出的那样,在与其他技术的交叉点上,模型焦点往往会丢失。
That line of code establishes the concept behind the operation. Of course, the Invoice objects probably aren’t in memory. There may be thousands of them. In a typical business system, the data is probably in a relational database. And, as pointed out in earlier chapters, the model focus tends to get lost at these intersections with other technologies.
关系数据库具有强大的搜索功能。我们如何利用这种能力来有效地解决这个问题,同时保留SPECIFICATION的模型?模型驱动设计要求模型与实现保持一致,但它允许自由选择任何忠实捕捉模型含义的实现。幸运的是,SQL 是一种非常自然的编写SPECIFICATION 的方式。
Relational databases have powerful search capabilities. How can we take advantage of that power to solve this problem efficiently while retaining the model of a SPECIFICATION? MODEL-DRIVEN DESIGN demands that the model stay in lockstep with the implementation, but it allows freedom to choose any implementation that faithfully captures the meaning of the model. Lucky for us, SQL is a very natural way to write SPECIFICATIONS.
这是一个简单的例子,其中查询与验证规则封装在同一个类中。向发票规范中添加了一个方法,并在Delinquent Invoice Specification子类中实现:
Here is a simple example, in which the query is encapsulated in the same class as the validation rule. A single method is added to the Invoice Specification and is implemented in the Delinquent Invoice Specification subclass:
公共字符串 asSQL() {
返回
“SELECT * FROM INVOICE, CUSTOMER”+
“ WHERE INVOICE.CUST_ID = CUSTOMER.ID”+
“ AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD”+
“ <”+ SQLUtility.dateAsSQL(currentDate);
}
public String asSQL() {
return
"SELECT * FROM INVOICE, CUSTOMER" +
" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +
" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" +
" < " + SQLUtility.dateAsSQL(currentDate);
}
规范 ( PECIFICATIONS)与存储库(REPOSITORIES)顺利配合,后者是提供对域对象的查询访问和封装数据库接口的构建块机制(见图9.15)。
SPECIFICATIONS mesh smoothly with REPOSITORIES, which are the building-block mechanisms for providing query access to domain objects and encapsulating the interface to the database (see Figure 9.15).
图 9.15. REPOSITORY和SPECIFICATION之间的交互
Figure 9.15. The interaction between REPOSITORY and SPECIFICATION
现在,这种设计存在一些问题。最重要的是,表结构的细节已经泄露到域层中;它们应该被隔离在将域对象与关系表关联起来的映射层中。在这里隐式地复制这些信息可能会损害Invoice和Customer对象的可修改性和可维护性,因为现在必须在多个地方跟踪对它们映射的任何更改。但这个例子只是简单地说明了如何将规则放在一个地方。一些对象关系映射框架提供了根据模型对象和属性来表达这种查询的方法,在基础设施层生成实际的 SQL。这样我们就可以鱼与熊掌兼得。
Now this design has some problems. Most important, the details of the table structure have leaked into the DOMAIN LAYER; they should be isolated in a mapping layer that relates the domain objects to the relational tables. Implicitly duplicating that information here could hurt the modifiability and maintainability of the Invoice and Customer objects, because any change to their mappings now have to be tracked in more than one place. But this example is a simple illustration of how to keep the rule in just one place. Some object-relational mapping frameworks provide the means to express such a query in terms of the model objects and attributes, generating the actual SQL in the infrastructure layer. This would let us have our cake and eat it too.
当基础设施无法解决问题时,我们可以通过向Invoice Repository添加专门的查询方法来重构表达域对象中的 SQL 。为了避免将规则嵌入到REPOSITORY中,我们必须以更通用的方式表达查询,这种方式不会捕获规则,但可以组合或放在上下文中以制定规则(在此示例中,通过使用双重分派)。
When the infrastructure doesn’t come to the rescue, we can refactor the SQL out of the expressive domain objects by adding a specialized query method to the Invoice Repository. To avoid embedding the rule into the REPOSITORY, we have to express the query in a more generic way, one that doesn’t capture the rule but can be combined or placed in context to work the rule out (in this example, by using a double dispatch).
public class InvoiceRepository {
public Set selectWhereGracePeriodPast(Date aDate){
//这不是规则,只是一个专门的查询
String sql = whereGracePeriodPast_SQL(aDate);
ResultSet queryResultSet =
SQLDatabaseInterface.instance().executeQuery(sql);
return buildInvoicesFromResultSet(queryResultSet);
}
public String whereGracePeriodPast_SQL(Date aDate) {
return
"SELECT * FROM INVOICE, CUSTOMER" +
" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +
" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" +
" < " + SQLUtility.dateAsSQL(aDate);
}
公共设置 selectSatisfying(InvoiceSpecification spec){
返回 spec.satisfyingElementsFrom(this);
}
}
public class InvoiceRepository {
public Set selectWhereGracePeriodPast(Date aDate){
//This is not a rule, just a specialized query
String sql = whereGracePeriodPast_SQL(aDate);
ResultSet queryResultSet =
SQLDatabaseInterface.instance().executeQuery(sql);
return buildInvoicesFromResultSet(queryResultSet);
}
public String whereGracePeriodPast_SQL(Date aDate) {
return
"SELECT * FROM INVOICE, CUSTOMER" +
" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +
" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" +
" < " + SQLUtility.dateAsSQL(aDate);
}
public Set selectSatisfying(InvoiceSpecification spec) {
return spec.satisfyingElementsFrom(this);
}
}
Invoice SpecificationasSql()中的方法被替换为,Delinquent Invoice Specification实现如下:satisfyingElementsFrom(InvoiceRepository)
The asSql() method on Invoice Specification is replaced with satisfyingElementsFrom(InvoiceRepository), which Delinquent Invoice Specification implements as:
public class DelinquentInvoiceSpecification {
//此处为基本 DelinquentInvoiceSpecification 代码
public Set SatisfactionElementsFrom(
InvoiceRepository Repository) {
//逾期规则定义为:
// “截至当前日期的宽限期已过”
return storage.selectWhereGracePeriodPast(currentDate);
}
}
public class DelinquentInvoiceSpecification {
// Basic DelinquentInvoiceSpecification code here
public Set satisfyingElementsFrom(
InvoiceRepository repository) {
//Delinquency rule is defined as:
// "grace period past as of current date"
return repository.selectWhereGracePeriodPast(currentDate);
}
}
这会将 SQL 放入REPOSITORY中,而SPECIFICATION控制应使用什么查询。规则并没有整齐地收集到SPECIFICATION中,但其中对构成拖欠(即超过宽限期)的基本声明是存在的。
This puts the SQL in the REPOSITORY, while the SPECIFICATION controls what query should be used. The rules aren’t as neatly collected into the SPECIFICATION, but the essential declaration is there of what constitutes delinquency (that is, past grace period).
REPOSITORY现在有一个非常专业的查询,很可能只会在这种情况下使用。这是可以接受的,但根据逾期发票与拖欠发票的相对数量,中间解决方案使REPOSITORY方法更通用,可能仍能提供良好的性能,同时使SPECIFICATION更加不言自明。
The REPOSITORY now has a very specialized query that most likely will be used only in this case. That is acceptable, but depending on the relative numbers of Invoices that are overdue compared to those that are delinquent, an intermediate solution that leaves the REPOSITORY methods more generic may still give good performance, while keeping the SPECIFICATION more self-explanatory.
公共类 InvoiceRepository {
公共设置 selectWhereDueDateIsBefore(Date aDate) {
字符串 sql = whereDueDateIsBefore_SQL(aDate);
结果集 queryResultSet =
SQLDatabaseInterface.instance().executeQuery(sql);
返回 buildInvoicesFromResultSet(queryResultSet);
}
public String whereDueDateIsBefore_SQL(Date aDate) {
return
"SELECT * FROM INVOICE" +
" WHERE INVOICE.DUE_DATE" +
" < " + SQLUtility.dateAsSQL(aDate);
}
public Set selectSatisfying(InvoiceSpecification spec) {
return spec.satisfyingElementsFrom(this);
}
}
public class DelinquentInvoiceSpecification {
//此处为基本 DelinquentInvoiceSpecification 代码
public Set SatisfyingElementsFrom(
InvoiceRepository storage) {
Collection pastDueInvoices =
storage.selectWhereDueDateIsBefore(currentDate);
Set delinquentInvoices = new HashSet();
Iterator it = pastDueInvoices.iterator();
while (it.hasNext()) {
Invoice anInvoice = (Invoice) it.next();
如果(this.isSatisfiedBy(anInvoice))
delinquentInvoices.add(anInvoice);
}
返回delinquentInvoices;
}
}
public class InvoiceRepository {
public Set selectWhereDueDateIsBefore(Date aDate) {
String sql = whereDueDateIsBefore_SQL(aDate);
ResultSet queryResultSet =
SQLDatabaseInterface.instance().executeQuery(sql);
return buildInvoicesFromResultSet(queryResultSet);
}
public String whereDueDateIsBefore_SQL(Date aDate) {
return
"SELECT * FROM INVOICE" +
" WHERE INVOICE.DUE_DATE" +
" < " + SQLUtility.dateAsSQL(aDate);
}
public Set selectSatisfying(InvoiceSpecification spec) {
return spec.satisfyingElementsFrom(this);
}
}
public class DelinquentInvoiceSpecification {
//Basic DelinquentInvoiceSpecification code here
public Set satisfyingElementsFrom(
InvoiceRepository repository) {
Collection pastDueInvoices =
repository.selectWhereDueDateIsBefore(currentDate);
Set delinquentInvoices = new HashSet();
Iterator it = pastDueInvoices.iterator();
while (it.hasNext()) {
Invoice anInvoice = (Invoice) it.next();
if (this.isSatisfiedBy(anInvoice))
delinquentInvoices.add(anInvoice);
}
return delinquentInvoices;
}
}
这段代码的性能会受到影响,因为我们会取出更多发票,然后必须在内存中进行选择。这是否是更好地分解责任的可接受成本完全取决于具体情况。有很多方法可以实现SPECIFICATIONS和REPOSITORIES之间的交互,以利用开发平台,同时保持基本职责不变。
We’ll take a performance hit with this code, because we pull out more Invoices and then have to select from them in memory. Whether this is an acceptable cost for the better factoring of responsibility depends entirely on circumstances. There are many ways to implement the interactions between SPECIFICATIONS and REPOSITORIES, to take advantage of the development platform, while keeping the basic responsibilities in place.
有时,为了提高性能,或者更可能是为了加强安全性,查询可能会在服务器上以存储过程的形式实现。在这种情况下,SPECIFICATION可以只携带存储过程允许的参数。尽管如此,这些不同实现之间的模型没有区别。实现的选择是自由的,除非模型有特别的限制。代价是编写和维护查询的方式更加繁琐。
Sometimes, to improve performance, or more likely to tighten security, queries may be implemented on the server as stored procedures. In that case, the SPECIFICATION could carry only the parameters allowed by the stored procedure. For all that, there is no difference in the model between these various implementations. The choice of implementation is free except where specifically constrained by the model. The price comes in a more cumbersome way of writing and maintaining queries.
这次讨论仅仅触及了将SPECIFICATIONS与数据库相结合所面临的挑战的表面,我不会试图涵盖可能出现的所有考虑因素。我只是想让您了解必须做出的选择。Mee 和 Hieatt 在 Fowler 2003 中讨论了使用SPECIFICATIONS设计REPOSITORIES所涉及的一些技术问题。
This discussion barely scratches the surface of the challenges of combining SPECIFICATIONS with databases, and I’ll make no attempt to cover all the considerations that may arise. I just want to give a taste of the kind of choices that have to be made. Mee and Hieatt discuss a few of the technical issues involved in designing REPOSITORIES with SPECIFICATIONS in Fowler 2003.
当五角大楼想要一架新型战斗机时,官员们会写一份规格说明书。该规格说明书可能要求喷气式飞机达到 2 马赫、航程为 1800 英里、成本不超过 5000 万美元等等。但无论规格说明书多么详细,它都不是飞机的设计,更不是飞机本身。一家航空航天工程公司将根据该规格说明书创建一个或多个设计。竞争公司可能会生产不同的设计,但所有这些设计都可能满足原始规格说明书。
When the Pentagon wants a new fighter jet, officials write a specification. This specification may require that the jet reach Mach 2, that it have a range of 1800 miles, that it cost no more than $50 million, and so on. But however detailed it is, the specification is not a design for a plane, much less a plane. An aerospace engineering company will take the specification and create one or more designs based on it. Competing companies may produce different designs, all of which presumably satisfy the original spec.
许多计算机程序都会生成一些东西,而这些东西必须指定。当你将一张图片放入文字处理文档时,文字会围绕图片流动。你已经指定了图片的位置,也许还指定了文本流动的样式。然后文字处理器会计算出页面上单词的确切位置,以符合你的规范。
Many computer programs generate things, and those things have to be specified. When you place a picture into a word-processing document, the text flows around it. You have specified the location of the picture, and perhaps the style of text flow. The exact placement of the words on the page is then worked out by the word processor in such a way that it meets your specification.
虽然乍一看可能不太明显,但这与应用于验证和选择的SPECIFICATION概念相同。我们正在为尚不存在的对象指定标准。但是,实现方式将大不相同。此SPECIFICATION不是用于预先存在的对象的过滤器(与查询不同)。它不是用于现有对象的测试(与验证不同)。这一次,将创建或重新配置一个全新的对象或一组对象以满足SPECIFICATION。
Although it may not be apparent at first, this is the same concept of a SPECIFICATION that was applied to validation and selection. We are specifying criteria for objects that are not yet present. The implementation will be quite different, however. This SPECIFICATION is not a filter for preexisting objects, as with querying. It is not a test for an existing object, as with validation. This time, a whole new object or set of objects will be made or reconfigured to satisfy the SPECIFICATION.
不使用SPECIFICATION,可以编写一个生成器,其中包含创建所需对象的程序或一组指令。此代码隐式定义了生成器的行为。
Without using SPECIFICATION, a generator can be written that has procedures or a set of instructions that create the needed objects. This code implicitly defines the behavior of the generator.
相反,根据描述性规范定义的生成器接口明确限制了生成器的产品。这种方法有几个优点。
Instead, an interface of the generator that is defined in terms of a descriptive SPECIFICATION explicitly constrains the generator’s products. This approach has several advantages.
•生成器的实现与其接口是分离的。SPECIFICATION声明了输出的要求,但没有定义如何达到该结果。
• The generator’s implementation is decoupled from its interface. The SPECIFICATION declares the requirements for the output but does not define how that result is reached.
• 接口明确传达其规则,因此开发人员无需了解生成器的所有操作细节即可知道该生成器会做什么。预测程序定义的生成器行为的唯一方法是运行案例或理解每一行代码。
• The interface communicates its rules explicitly, so developers can know what to expect from the generator without understanding all details of its operation. The only way to predict the behavior of a procedurally defined generator is to run cases or to understand every line of code.
• 接口更加灵活,或者可以通过更大的灵活性进行增强,因为请求的声明掌握在客户端手中,而生成器仅有义务履行规范的文字。
• The interface is more flexible, or can be enhanced with more flexibility, because the statement of the request is in the hands of the client, while the generator is only obligated to fulfill the letter of the SPECIFICATION.
• 最后,但并非最不重要的一点是,这种接口更易于测试,因为模型包含一种显式方法来定义生成器的输入,这也是对输出的验证。也就是说,传递到生成器接口以约束创建过程的相同SPECIFICATION也可以在其验证角色(如果实现支持它)中用于确认创建的对象是否正确。(这是ASSERTION的一个例子,在第 10 章中讨论。)
• Last, but not least, this kind of interface is easier to test, because the model contains an explicit way to define input into the generator that is also a validation of the output. That is, the same SPECIFICATION that is passed into the generator’s interface to constrain the creation process can also be used, in its validation role (if the implementation supports it) to confirm that the created object is correct. (This is an example of an ASSERTION, discussed in Chapter 10.)
按订单建造可以意味着从头开始创建一个对象,但也可以是对预先存在的对象进行配置以满足SPEC。
Building to order can mean creation of an object from scratch, but it can also be a configuration of preexisting objects to satisfy the SPEC.
有一个仓库,里面堆放着各种化学品,这些化学品被存放在类似于箱车的大型容器中。有些化学品是惰性的,几乎可以存放在任何地方。有些化学品易挥发,必须存放在专门通风的容器中。有些化学品易爆炸,必须存放在专门的装甲容器中。容器中允许的组合也有规定。
There is a warehouse in which various chemicals are stored in stacks of large containers, similar to boxcars. Some chemicals are inert and can be stored just about anywhere. Some are volatile and have to be stored in specially ventilated containers. Some are explosive and have to be stored in specially armored containers. There are also rules about the combinations allowed in a container.
目标是编写软件来找到一种有效且安全的方法将化学品放入容器中。
The goal is to write software that will find an efficient and safe way to put the chemicals in the containers.
图 9.16. 仓库存储模型
Figure 9.16. A model for warehouse storage
我们可以先编写一个程序来取出化学物质并将其放入容器中,但我们先从验证问题开始。这将迫使我们明确规则,并为我们提供一种测试最终实现的方法。
We could start by writing a procedure to take a chemical and place it in a container, but instead, let’s start with the validation problem. This will force us to make the rules explicit, and it will give us a way to test the final implementation.
每种化学品都有一个容器规格:
Each chemical will have a container SPECIFICATION:
现在,如果我们将这些写为“集装箱规范”,我们应该能够采用包装集装箱的配置并测试它是否满足这些约束。
Now, if we write these as Container Specifications, we should be able to take a configuration of packed containers and test to see if it meets these constraints.
必须实现容器规范上的方法来检查所需的容器特征。例如,附在爆炸性化学品上的 SPEC 将查找“装甲”特征:isSatisfied()
A method on Container Specification, isSatisfied(), would have to be implemented to check for needed ContainerFeatures. For example, the SPEC attached to an explosive chemical would look for the “armored” feature:
公共类 ContainerSpecification {
私有 ContainerFeature requiredFeature;
公共 ContainerSpecification(ContainerFeature 必需) {
requiredFeature = required;
}
boolean isSatisfiedBy(Container aContainer){
return aContainer.getFeatures().contains(requiredFeature);
}
}
public class ContainerSpecification {
private ContainerFeature requiredFeature;
public ContainerSpecification(ContainerFeature required) {
requiredFeature = required;
}
boolean isSatisfiedBy(Container aContainer){
return aContainer.getFeatures().contains(requiredFeature);
}
}
以下是设置爆炸性化学品的示例客户端代码:
Here is sample client code to set up an explosive chemical:
tnt.设置容器规范(
新容器规范(ARMORED));
tnt.setContainerSpecification(
new ContainerSpecification(ARMORED));
容器对象上的方法isSafelyPacked(),将确认容器具有其所含化学品指定的所有特性:
A method on a Container object, isSafelyPacked(), will confirm that Container has all the features specified by the Chemicals it contains:
boolean isSafelyPacked(){
Iterator it = content.iterator();
while (it.hasNext()) {
Drum drum = (Drum) it.next();
if (!drum.containerSpecification().isSatisfiedBy(this))
return false;
}
return true;
}
boolean isSafelyPacked(){
Iterator it = contents.iterator();
while (it.hasNext()) {
Drum drum = (Drum) it.next();
if (!drum.containerSpecification().isSatisfiedBy(this))
return false;
}
return true;
}
此时,我们可以编写一个监控应用程序,获取库存数据库并报告任何不安全的情况。
At this point, we could write a monitoring application that would take the inventory database and report any unsafe situations.
迭代器 it = containers.iterator();
while (it.hasNext()) {
容器 container = (容器) it.next();
if (!container.isSafelyPacked())
unsafeContainers.add(container);
}
Iterator it = containers.iterator();
while (it.hasNext()) {
Container container = (Container) it.next();
if (!container.isSafelyPacked())
unsafeContainers.add(container);
}
这不是我们被要求编写的软件。让业务人员知道这个机会会很好,但我们被委托设计一个打包机。我们有一个打包机测试。对领域的理解和我们基于SPECIFICATION的模型使我们能够为服务定义一个清晰而简单的接口,该服务将收集桶和容器并按照规则对其进行打包。
This is not the software we’ve been asked to write. It would be good to let the business people know about the opportunity, but we have been charged with designing a packer. What we have is a test for a packer. This understanding of the domain and our SPECIFICATION-based model put us in a position to define a clear and simple interface for a SERVICE that will take collections of Drums and Containers and pack them in compliance with the rules.
public interface WarehousePacker {
public void pack(Collection containersToFill,
Collection drumsToPack) throws NoAnswerFoundException;
/* 断言:在 pack() 结束时,每个 Drum 的 ContainerSpecification
应由其容器满足。
如果找不到完整的解决方案,则应
抛出异常。 */
}
public interface WarehousePacker {
public void pack(Collection containersToFill,
Collection drumsToPack) throws NoAnswerFoundException;
/* ASSERTION: At end of pack(), the ContainerSpecification
of each Drum shall be satisfied by its Container.
If no complete solution can be found, an exception shall
be thrown. */
}
现在,设计一个优化的约束求解器来履行Packer服务职责的任务已经与应用程序的其余部分分离,并且这些机制不会使表达模型的设计部分变得混乱。(请参阅第10 章“声明式设计风格”和第 15 章“内聚机制”。 )然而,管理打包的规则尚未从域对象中提取出来。
Now the task of designing an optimized constraint solver to fulfill the responsibilities of the Packer service has been decoupled from the rest of the application, and those mechanisms will not clutter the part of the design that expresses the model. (See “Declarative Style of Design,” Chapter 10, and COHESIVE MECHANISM, Chapter 15.) Yet the rules governing packing have not been pulled out of the domain objects.
编写优化逻辑以使仓库包装软件正常工作是一项艰巨的工作。一个由开发人员和业务专家组成的小团队已经分头开始着手这项工作,但他们甚至还没有开始编写代码。与此同时,另一个小团队正在开发应用程序,该应用程序将允许用户从数据库中提取库存,将其提供给 Packer并解释结果。他们正在尝试为预期的Packer进行设计。但他们所能做的只是模拟一个 UI 并编写一些数据库集成代码。他们无法向用户展示具有有意义行为的界面以获得良好的反馈。出于同样的原因,Packer团队也在真空中工作。
Writing the optimization logic to make the warehouse packing software work is a big job. A small team of developers and business experts have split off and have set to work on it, but they haven’t even begun to code. Meanwhile, another small team is developing the application that will allow users to pull inventory from the database, feed it to the Packer, and interpret the results. They are trying to design for the anticipated Packer. But all they can do is mock up a UI and work on some database integration code. They can’t show the users an interface with meaningful behavior to get good feedback. For the same reason, the Packer team is working in a vacuum too.
借助仓库包装机示例中的域对象和服务接口,应用程序团队意识到他们可以构建一个非常简单的包装机实现,这可以帮助推进开发过程,使工作并行进行并关闭反馈循环,而这只有在端到端系统中才能达到全部效果。
With the domain objects and SERVICE interface made in the warehouse packer example, the application team realizes they could build a very simple implementation of a Packer that could help the development process move along, allowing work to go forward in parallel and closing the feedback loop, which only reaches full effect with a working end-to-end system.
公共类 Container {
私有双容量;
私有设置内容; //鼓
公共布尔 hasSpaceFor(Drum aDrum) {
return remainingSpace() >= aDrum.getSize();
}
公共双剩余空间() {
double totalContentSize = 0.0;
迭代器 it = content.iterator();
while (it.hasNext()) {
鼓 aDrum = (Drum) it.next();
totalContentSize = totalContentSize + aDrum.getSize();
}
返回容量 – totalContentSize;
}
公共布尔 canAccommodate(Drum aDrum) {
return hasSpaceFor(aDrum) &&
aDrum.getContainerSpecification().isSatisfiedBy(this);
}
}
公共类 PrototypePacker 实现 WarehousePacker {
公共 void pack(Collection containers, Collection drums)
抛出 NoAnswerFoundException {
/*此方法满足所写的 ASSERTION。但是,
当抛出异常时,容器的内容可能
已经改变。必须在更高级别处理回滚
。 */
Iterator it = drums.iterator();
while (it.hasNext()) {
Drum drum = (Drum) it.next();
Container container =
findContainerFor(containers, drum);
container.add(drum);
}
}
public Container findContainerFor(
Collection containers, Drum drum)
throws NoAnswerFoundException {
Iterator it = containers.iterator();
while (it.hasNext()) {
Container container = (Container) it.next();
if (container.canAccommodate(drum))
return container;
}
throw new NoAnswerFoundException();
}
}
public class Container {
private double capacity;
private Set contents; //Drums
public boolean hasSpaceFor(Drum aDrum) {
return remainingSpace() >= aDrum.getSize();
}
public double remainingSpace() {
double totalContentSize = 0.0;
Iterator it = contents.iterator();
while (it.hasNext()) {
Drum aDrum = (Drum) it.next();
totalContentSize = totalContentSize + aDrum.getSize();
}
return capacity – totalContentSize;
}
public boolean canAccommodate(Drum aDrum) {
return hasSpaceFor(aDrum) &&
aDrum.getContainerSpecification().isSatisfiedBy(this);
}
}
public class PrototypePacker implements WarehousePacker {
public void pack(Collection containers, Collection drums)
throws NoAnswerFoundException {
/* This method fulfills the ASSERTION as written. However,
when an exception is thrown, Containers' contents may
have changed. Rollback must be handled at a higher
level. */
Iterator it = drums.iterator();
while (it.hasNext()) {
Drum drum = (Drum) it.next();
Container container =
findContainerFor(containers, drum);
container.add(drum);
}
}
public Container findContainerFor(
Collection containers, Drum drum)
throws NoAnswerFoundException {
Iterator it = containers.iterator();
while (it.hasNext()) {
Container container = (Container) it.next();
if (container.canAccommodate(drum))
return container;
}
throw new NoAnswerFoundException();
}
}
当然,这个代码还有很多不足之处。它可能会将沙子装入专用容器,然后在包装危险化学品之前就用完了空间。它当然不会优化收入。但无论如何,很多优化问题永远无法完美解决。此实现确实遵循了迄今为止所述的规则。
Granted that this code leaves a lot to be desired. It might pack sand into specialty containers and then run out of room before it packs the hazardous chemicals. It certainly doesn’t optimize revenues. But a lot of optimization problems are never solved perfectly anyway. This implementation does follow the rules that have been stated so far.
有了这个原型,应用程序开发人员就可以全速前进,包括与外部系统的所有集成。Packer开发团队还会得到反馈,因为领域专家会与原型互动并巩固他们的想法,帮助澄清需求和优先事项。Packer团队决定接管原型并对其进行调整以测试想法。
Having this prototype lets the application developers move at full speed, including all integrations with external systems. The Packer development team also gets feedback as domain experts interact with the prototype and firm up their ideas, helping clarify requirements and priorities. The Packer team decides to take over the prototype and tweak it to test ideas.
他们还使界面保持最新的设计,强制重构应用程序和一些领域对象,从而尽早解决集成问题。
They also keep the interface up-to-date with their latest design, forcing refactoring of the application, and some domain objects, thereby tackling the integration problems early.
一旦复杂的Packer准备就绪,集成就会变得轻而易举,因为它已经被写入一个特征明确的接口 - 与原型交互时编写的应用程序的接口和断言相同。
As soon as the sophisticated Packer is ready, integration is a breeze because it has been written to a well-characterized interface—the same interface and ASSERTIONS that the application was written for when interacting with the prototype.
优化算法专家花了几个月的时间才将其解决。他们受益于用户与原型交互的反馈。与此同时,系统的所有其他部分在开发过程中都有可以交互的内容。
It took specialists in optimization algorithms months to get it right. They benefited from the feedback they could get from users interacting with the prototype. In the meantime, all other parts of the system have something to interact with during development.
这里我们有一个“最简单的可能行得通的东西”的例子,它实际上因为一个更复杂的模型而成为可能。我们可以有一个非常复杂的功能原型只需几十行易于理解的代码即可完成组件。模型驱动程度较低的方法将更难理解,更难升级(因为 Packer与设计的其余部分耦合性更强),并且在这种情况下,可能需要更长的时间来制作原型。
Here we have an example of a “simplest thing that could possibly work” that actually becomes possible because of a more sophisticated model. We can have a functioning prototype of a very complex component in a couple dozen lines of easily understood code. A less MODEL-DRIVEN approach would be harder to understand, would be harder to upgrade (because the Packer would be more coupled to the rest of the design), and in this case, would likely take longer to prototype.
软件的最终目的是为用户服务。但首先,软件必须为开发人员服务。在强调重构的过程中尤其如此。随着程序的发展,开发人员将重新安排和重写每个部分。他们将域对象与新域对象集成到应用程序中。即使多年后,维护程序员也会更改和扩展代码。人们必须处理这些东西。但他们愿意吗?
The ultimate purpose of software is to serve users. But first, that same software has to serve developers. This is especially true in a process that emphasizes refactoring. As a program evolves, developers will rearrange and rewrite every part. They will integrate the domain objects into the application and with new domain objects. Even years later, maintenance programmers will be changing and extending the code. People have to work with this stuff. But will they want to?
当具有复杂行为的软件缺乏良好的设计时,重构或组合元素会变得困难。一旦开发人员无法自信地预测计算的全部含义,就会开始出现重复。当设计元素是单片的,以至于无法重新组合各个部分时,就会强制出现重复。类和方法可以分解以便更好地重用,但很难跟踪所有小部件的作用。当软件没有清晰的设计时,开发人员甚至害怕看到现有的混乱,更不用说做出可能加剧混乱或通过不可预见的依赖关系破坏某些东西的改变。在任何系统中,除了最小的系统,这种脆弱性都会限制可构建的行为的丰富性。它阻止了重构和迭代改进。
When software with complex behavior lacks a good design, it becomes hard to refactor or combine elements. Duplication starts to appear as soon as a developer isn’t confident of predicting the full implications of a computation. Duplication is forced when design elements are monolithic, so that the parts cannot be recombined. Classes and methods can be broken down for better reuse, but it gets hard to keep track of what all the little parts do. When software doesn’t have a clean design, developers dread even looking at the existing mess, much less making a change that could aggravate the tangle or break something through an unforeseen dependency. In any but the smallest systems, this fragility places a ceiling on the richness of behavior it is feasible to build. It stops refactoring and iterative refinement.
要让项目在开发过程中加速发展,而不是被自身遗留的问题所拖累,就需要一种令人愉悦、乐于接受改变的设计。一种灵活的设计。
To have a project accelerate as development proceeds—rather than get weighed down by its own legacy—demands a design that is a pleasure to work with, inviting to change. A supple design.
灵活的设计是对深度建模的补充。一旦你挖掘出隐含的概念并将它们明确化,你就拥有了原始材料。通过迭代周期,你可以将这些材料锤炼成有用的形状,培养一个简单而清晰地捕捉关键问题的模型,并塑造一个允许客户端开发人员真正使用该模型的设计。设计和代码的开发会带来洞察力,从而改进模型概念。周而复始——我们回到迭代周期并重构以获得更深入的洞察力。但是你想要实现什么样的设计?你应该在此过程中尝试什么样的实验?这就是本章的内容。
Supple design is the complement to deep modeling. Once you’ve dug out implicit concepts and made them explicit, you have the raw material. Through the iterative cycle, you hammer that material into a useful shape, cultivating a model that simply and clearly captures the key concerns, and shaping a design that allows a client developer to really put that model to work. Development of the design and code leads to insight that refines model concepts. Round and round—we’re back to the iterative cycle and refactoring toward deeper insight. But what kind of design are you trying to arrive at? What kind of experiments should you try along the way? That is what this chapter is about.
很多过度设计都是以灵活性为名进行。但更多时候,过多的抽象和间接层会造成阻碍。看看那些真正赋予处理它的人权力的软件设计;你通常会看到一些简单的东西。简单并不容易。要创建可以组装成复杂系统且仍然易于理解的元素,必须将对模型驱动设计的专注与适度严格的设计风格结合起来。创建或使用可能需要相对复杂的设计技巧。
A lot of overengineering has been justified in the name of flexibility. But more often than not, excessive layers of abstraction and indirection get in the way. Look at the design of software that really empowers the people who handle it; you will usually see something simple. Simple is not easy. To create elements that can be assembled into elaborate systems and still be understandable, a dedication to MODEL-DRIVEN DESIGN has to be joined with a moderately rigorous design style. It may well require relatively sophisticated design skill to create or to use.
开发人员扮演两种角色,每种角色都必须由设计服务。同一个人可能会同时扮演这两种角色,甚至几分钟内就可以来回切换,但与代码的关系却不同。一个角色是客户端的开发人员,他利用设计的功能将域对象编织到应用程序代码或其他域层代码中。灵活的设计揭示了一个深层的底层模型,使其潜力清晰可见。客户端开发人员可以灵活地使用一组最小的松散耦合概念来表达域中的一系列场景。设计元素以自然的方式组合在一起,结果是可预测的、特征鲜明的和健壮的。
Developers play two roles, each of which must be served by the design. The same person might well play both roles—even switch back and forth in minutes—but the relationship to the code is different nonetheless. One role is the developer of a client, who weaves the domain objects into the application code or other domain layer code, utilizing capabilities of the design. A supple design reveals a deep underlying model that makes its potential clear. The client developer can flexibly use a minimal set of loosely coupled concepts to express a range of scenarios in the domain. Design elements fit together in a natural way with a result that is predictable, clearly characterized, and robust.
同样重要的是,设计必须服务于致力于改变它的开发人员。要接受改变,设计必须易于理解,揭示客户端开发人员所依赖的相同底层模型。它必须遵循领域深层模型的轮廓,因此大多数更改都会在灵活的点上改变设计。其代码的影响必须显而易见,因此更改的后果将很容易预测。
Equally important, the design must serve the developer working to change it. To be open to change, a design must be easy to understand, revealing that same underlying model that the client developer is drawing on. It must follow the contours of a deep model of the domain, so most changes bend the design at flexible points. The effects of its code must be transparently obvious, so the consequences of a change will be easy to anticipate.
设计的早期版本通常比较僵硬。许多版本在项目的时间框架或预算内从未获得任何灵活性。我从未见过一个大型项目始终具有这种品质。但是,当复杂性阻碍进展时,将最关键、最复杂的部分磨练成灵活的设计,是陷入遗留维护还是突破复杂性上限之间的区别。
Early versions of a design are usually stiff. Many never acquire any suppleness in the time frame or budget of the project. I’ve never seen a large program that had this quality throughout. But when complexity is holding back progress, honing the most crucial, intricate parts to a supple design makes the difference between getting sucked down into legacy maintenance and punching through the complexity ceiling.
设计这样的软件没有固定的公式,但我挑选了一组模式,根据我的经验,这些模式在合适时往往会为设计带来灵活性。这些模式和示例应该能让您感受到什么是灵活的设计,以及其中涉及的思维方式。
There is no formula for designing software like this, but I have culled a set of patterns that, in my experience, tend to lend suppleness to a design when they fit. These patterns and examples should give a feel for what a supple design is like and the kind of thinking that goes into it.
图 10.1. 一些有助于实现柔性设计的模式
Figure 10.1. Some patterns that contribute to supple design
在领域驱动设计中,我们想要思考有意义的领域逻辑。代码如果产生规则效果但没有明确说明规则,就会迫使我们思考逐步的软件程序。这同样适用于仅通过运行某些代码得出但并不明确的计算。如果没有与模型的明确联系,就很难理解代码的效果或预测更改的效果。上一章明确地深入探讨了建模规则和计算。实现这样的对象需要大量了解计算的细节或规则的细节。对象的美妙之处在于它们能够封装所有这些,因此客户端代码很简单,可以用更高级的概念来解释。
In domain-driven design, we want to think about meaningful domain logic. Code that produces the effect of a rule without explicitly stating the rule forces us to think of step-by-step software procedures. The same applies to a calculation that just results from running some code, but isn’t explicit. Without a clear connection to the model, it is difficult to understand the effect of the code or anticipate the effect of a change. The previous chapter delved into modeling rules and calculations explicitly. Implementing such objects requires a lot of understanding of the gritty details of the calculation or the fine print of the rule. The beauty of objects is their ability to encapsulate all that, so that client code is simple and can be interpreted in terms of higher-level concepts.
但是,如果接口没有告诉客户端开发人员他需要知道什么才能有效地使用对象,那么他无论如何都必须深入研究内部结构以了解细节。客户端代码的读者也必须这样做。然后封装的大部分价值就会丧失。我们一直在与认知超负荷作斗争:如果客户端开发人员的头脑中充斥着有关组件如何工作的细节,他的头脑就无法清晰地理解客户端设计的复杂性。即使同一个人同时扮演两个角色,开发和使用自己的代码,情况也是如此,因为即使他不必学习这些细节,他一次能考虑的因素数量也是有限的。
But if the interface doesn’t tell the client developer what he needs to know in order to use the object effectively, he will have to dig into the internals to understand the details anyway. A reader of the client code will have to do the same. Then most of the value of the encapsulation is lost. We are always fighting cognitive overload: If the client developer’s mind is flooded with detail about how a component does its job, his mind isn’t clear to work out the intricacies of the client design. This is true even when the same person is playing both roles, developing and using his own code, because even if he doesn’t have to learn those details, there is a limit to how many factors he can consider at once.
如果开发人员必须考虑组件的实现才能使用它,那么封装的价值就丧失了。如果原始开发人员以外的其他人必须根据对象或操作的实现推断其目的,那么新开发人员可能会推断出该操作或类只是偶然实现的目的。如果这不是意图,代码可能暂时可以工作,但设计的概念基础将被破坏,并且两个开发人员将各行其是。
If a developer must consider the implementation of a component in order to use it, the value of encapsulation is lost. If someone other than the original developer must infer the purpose of an object or operation based on its implementation, that new developer may infer a purpose that the operation or class fulfills only by chance. If that was not the intent, the code may work for the moment, but the conceptual basis of the design will have been corrupted, and the two developers will be working at cross-purposes.
为了获得以类或方法的形式显式建模概念的价值,我们必须为这些程序元素赋予能够反映这些概念的名称。类和方法的名称是改善开发人员之间沟通以及改进系统抽象的绝佳机会。
To obtain the value of explicitly modeling a concept in the form of a class or method, we must give these program elements names that reflect those concepts. The names of classes and methods are great opportunities for improving communication between developers, and for improving the abstraction of the system.
Kent Beck 写道,使用“意图揭示选择器”让方法名称传达其目的(Beck 1997)。设计的所有公共元素共同构成其接口,每个元素的名称都提供了揭示设计意图的机会。类型名称、方法名称和参数名称都组合在一起形成“意图揭示接口”。
Kent Beck wrote of making method names communicate their purpose with an INTENTION-REVEALING SELECTOR (Beck 1997). All public elements of a design together make up its interface, and the name of each of those elements presents an opportunity to reveal the intention of the design. Type names, method names, and argument names all combine to form an INTENTION-REVEALING INTERFACE.
所以:
Therefore:
命名类和操作以描述其效果和目的,而不提及它们实现承诺的方式。这样客户端开发人员就无需了解内部结构。这些名称应符合 UBIQUITOUS LANGUAGE,以便团队成员可以快速推断其含义。在创建行为之前,先编写测试,以强制您进入客户端开发人员模式思考。
Name classes and operations to describe their effect and purpose, without reference to the means by which they do what they promise. This relieves the client developer of the need to understand the internals. These names should conform to the UBIQUITOUS LANGUAGE so that team members can quickly infer their meaning. Write a test for a behavior before creating it, to force your thinking into client developer mode.
所有棘手的机制都应该封装在抽象接口后面,这些抽象接口以意图而不是手段来表达。
All the tricky mechanism should be encapsulated behind abstract interfaces that speak in terms of intentions, rather than means.
在领域的公共接口中,陈述关系和规则,但不说明如何执行;描述事件和动作,但不说明如何执行;制定方程式,但不说明求解该方程式的数值方法。提出问题,但不说明找到答案的方法。
In the public interfaces of the domain, state relationships and rules, but not how they are enforced; describe events and actions, but not how they are carried out; formulate the equation but not the numerical method to solve it. Pose the question, but don’t present the means by which the answer shall be found.
油漆店的程序可以向顾客展示混合标准油漆的结果。这是初始设计,它有一个域类。
A program for paint stores can show a customer the result of mixing standard paints. Here is the initial design, which has a single domain class.
图 10.2
Figure 10.2
猜测该paint(Paint)方法功能的唯一方法就是阅读代码。
The only way to even guess what the paint(Paint) method does is to read the code.
public void paint(Paint paint) {
v = v + paint.getV(); //混合后,体积相加
// 省略了许多行复杂的颜色混合逻辑
// 以分配新的 r、b 和 y 值结束。
}
public void paint(Paint paint) {
v = v + paint.getV(); //After mixing, volume is summed
// Omitted many lines of complicated color mixing logic
// ending with the assignment of new r, b, and y values.
}
好的,所以看起来这种方法将两种Paint结合在一起,结果具有更大的体积和混合的颜色。
OK, so it looks like this method combines two Paints together, the result having a larger volume and a mixed color.
为了改变我们的观点,让我们为这个方法编写一个测试。(此代码基于 JUnit 测试框架。)
To shift our perspective, let’s write a test for this method. (This code is based on the JUnit test framework.)
public void testPaint() {
// 创建纯黄色颜料,体积为 100
Paint yellow = new Paint(100.0, 0, 50, 0);
// 创建纯蓝色颜料,体积为 100
Paint blue = new Paint(100.0, 0, 0, 50);
// 将蓝色混入黄色
yellow.paint(blue);
// 结果应为绿色油漆的体积为 200.0
assertEquals(200.0, yellow.getV(), 0.01);
assertEquals(25, yellow.getB());
assertEquals(25, yellow.getY());
assertEquals(0, yellow.getR());
}
public void testPaint() {
// Create a pure yellow paint with volume=100
Paint yellow = new Paint(100.0, 0, 50, 0);
// Create a pure blue paint with volume=100
Paint blue = new Paint(100.0, 0, 0, 50);
// Mix the blue into the yellow
yellow.paint(blue);
// Result should be volume of 200.0 of green paint
assertEquals(200.0, yellow.getV(), 0.01);
assertEquals(25, yellow.getB());
assertEquals(25, yellow.getY());
assertEquals(0, yellow.getR());
}
通过测试只是起点。此时它并不令人满意,因为测试中的代码没有告诉我们它在做什么。让我们重写测试以反映我们在编写客户端应用程序时希望使用 Paint 对象的方式。最初,此测试将失败。事实上,它甚至不会编译。我们编写它是为了从客户端开发人员的角度探索Paint对象的接口设计。
The passing test is the starting point. It is unsatisfying at this point because the code in the test doesn’t tell us what it is doing. Let’s rewrite the test to reflect the way we would like to use the Paint objects if we were writing a client application. Initially, this test will fail. In fact, it won’t even compile. We are writing it to explore the interface design of the Paint object from the client developer’s point of view.
public void testPaint() {
// 从体积为 100 的纯黄色颜料开始
Paint ourPaint = new Paint(100.0, 0, 50, 0);
// 取体积为 100 的纯蓝色颜料
Paint blue = new Paint(100.0, 0, 0, 50);
// 将蓝色颜料混入黄色
ourPaint.mixIn(blue);
// 结果应为体积为 200.0 的绿色颜料
assertEquals(200.0, ourPaint.getVolume(), 0.01);
assertEquals(25, ourPaint.getBlue());
assertEquals(25, ourPaint.getYellow());
assertEquals(0, ourPaint.getRed());
}
public void testPaint() {
// Start with a pure yellow paint with volume=100
Paint ourPaint = new Paint(100.0, 0, 50, 0);
// Take a pure blue paint with volume=100
Paint blue = new Paint(100.0, 0, 0, 50);
// Mix the blue into the yellow
ourPaint.mixIn(blue);
// Result should be volume of 200.0 of green paint
assertEquals(200.0, ourPaint.getVolume(), 0.01);
assertEquals(25, ourPaint.getBlue());
assertEquals(25, ourPaint.getYellow());
assertEquals(0, ourPaint.getRed());
}
我们应该花时间编写一个测试来反映我们想要与这些对象对话的方式。之后,我们重构Paint类以使测试通过。
We should take our time to write a test that reflects the way we would like to talk to these objects. After that, we refactor the Paint class to make the test pass.
图 10.3
Figure 10.3
新方法的名称可能无法告诉读者“混合”另一个Paint的效果(为此,我们需要ASSERTIONS,后面几页会讲到)。但它将为读者提供足够的线索,帮助他们开始使用该类,尤其是使用测试提供的示例。并且它将允许客户端代码的读者解释客户端的意图。在本章接下来的几个示例中,我们将再次重构该类,使其更加清晰。
The new method name may not tell the reader everything about the effect of “mixing in” another Paint (for that we’ll need ASSERTIONS, coming up in a few pages). But it will clue the reader in enough to get started using the class, especially with the example the test provides. And it will allow the reader of the client code to interpret the client’s intent. In the next few examples in this chapter, we’ll refactor this class again to make it even clearer.
整个子域可以分割成单独的模块,并封装在意图揭示接口后面。使用这种削减来集中项目和管理大型系统的复杂性将在第 15 章“提炼”中讨论,其中包含内聚机制和通用子域。
Entire subdomains can be carved off into separate modules and encapsulated behind INTENTION-REVEALING INTERFACES. Using such whittling to focus a project and manage the complexity of a large system will be discussed more in Chapter 15, “Distillation,” with COHESIVE MECHANISMS and GENERIC SUBDOMAINS.
但在接下来的两种模式中,我们将着手使使用方法的后果非常可预测。复杂逻辑可以在无副作用函数中安全地完成。改变系统状态的方法可以用断言来描述。
But in the next two patterns, we’ll set out to make the consequences of using a method very predictable. Complex logic can be done safely in SIDE-EFFECT-FREE FUNCTIONS. Methods that change system state can be characterized with ASSERTIONS.
操作大致可分为两类:命令和查询。查询从系统获取信息,可能只是访问变量中的数据,也可能基于该数据执行计算。命令(也称为修饰符)是影响系统某些变化的操作(举个简单的例子,通过设置变量)。在标准英语中,术语副作用意味着意想不到的后果,但在计算机科学中,它意味着对系统状态的任何影响。为了我们的目的,让我们将其含义缩小到会影响未来操作的任何系统状态变化。
Operations can be broadly divided into two categories, commands and queries. Queries obtain information from the system, possibly by simply accessing data in a variable, possibly performing a calculation based on that data. Commands (also known as modifiers) are operations that affect some change to the systems (for a simple example, by setting a variable). In standard English, the term side effect implies an unintended consequence, but in computer science, it means any effect on the state of the system. For our purposes, let’s narrow that meaning to any change in the state of the system that will affect future operations.
为什么采用“副作用”这个术语并将其应用于受操作影响的非常有意的更改?我认为这是基于对复杂系统的经验。大多数操作都会调用其他操作,而那些被调用的操作又会调用其他操作。一旦涉及到这种任意深度的嵌套,就很难预料调用操作的所有后果。客户端的开发人员可能没有想到第二层和第三层操作的效果——它们已经成为名副其实的“副作用”。复杂设计的元素以其他方式相互作用,可能会产生相同的不可预测性。使用“副作用”这个术语强调了这种相互作用的必然性。
Why was the term side effect adopted and applied to quite intentional changes affected by operations? I assume this was based on experience with complex systems. Most operations call on other operations, and those called invoke still other operations. As soon as this arbitrarily deep nesting is involved, it becomes very hard to anticipate all the consequences of invoking an operation. The developer of the client may not have intended the effects of the second-tier and third-tier operations—they’ve become side effects in every sense of the phrase. Elements of a complex design interact in other ways that are likely to produce the same unpredictability. The use of the term side effect underlines the inevitability of that interaction.
多个规则或计算组合的交互变得极难预测。调用操作的开发人员必须了解其实现及其所有委托的实现,才能预测结果。如果开发人员被迫揭开面纱,任何接口抽象的用处都会受到限制。如果没有安全可预测的抽象,开发人员必须限制组合爆炸,从而为可构建的行为的丰富性设置一个较低的上限。
Interactions of multiple rules or compositions of calculations become extremely difficult to predict. The developer calling an operation must understand its implementation and the implementation of all its delegations in order to anticipate the result. The usefulness of any abstraction of interfaces is limited if the developers are forced to pierce the veil. Without safely predictable abstractions, the developers must limit the combinatory explosion, placing a low ceiling on the richness of behavior that is feasible to build.
返回结果而不产生副作用的操作称为函数。一个函数可以被多次调用,每次都返回相同的值。一个函数可以调用其他函数而不必担心嵌套深度。与具有副作用的操作相比,函数更容易测试。出于这些原因,函数的风险较低。
Operations that return results without producing side effects are called functions. A function can be called multiple times and return the same value each time. A function can call on other functions without worrying about the depth of nesting. Functions are much easier to test than operations that have side effects. For these reasons, functions lower risk.
显然,在大多数软件系统中,您无法避免使用命令,但可以通过两种方式缓解此问题。首先,您可以将命令和查询严格隔离在不同的操作中。确保导致更改的方法不会返回域数据,并尽可能保持简单。在不会引起可观察到的副作用的方法中执行所有查询和计算(Meyer 1988)。
Obviously, you can’t avoid commands in most software systems, but the problem can be mitigated in two ways. First, you can keep the commands and queries strictly segregated in different operations. Ensure that the methods that cause changes do not return domain data and are kept as simple as possible. Perform all queries and calculations in methods that cause no observable side effects (Meyer 1988).
其次,通常存在替代模型和设计,它们根本不需要修改现有对象。相反,会创建并返回一个新的VALUE OBJECT,表示计算结果。这是一种常用技术,将在后面的示例中进行说明。VALUE OBJECT可以作为查询的答案而创建,然后交出并被遗忘 — 这与ENTITY不同,后者的生命周期受到严格监管。
Second, there are often alternative models and designs that do not call for an existing object to be modified at all. Instead, a new VALUE OBJECT, representing the result of the computation, is created and returned. This is a common technique, which will be illustrated in the example that follows. A VALUE OBJECT can be created in answer to a query, handed off, and forgotten—unlike an ENTITY, whose life cycle is carefully regulated.
V ALUE OBJECTS是不可变的,这意味着除了在创建期间调用的初始化程序之外,它们的所有操作都是函数。 V ALUE OBJECTS与函数一样,使用起来更安全,也更容易测试。将逻辑或计算与状态改变混合的操作应该重构为两个独立的操作(Fowler 1999,第 279 页)。但根据定义,将副作用分离为简单命令方法的做法仅适用于ENTITIES。在完成将修改与查询分离的重构后,可以考虑进行第二次重构,将复杂计算的责任转移到VALUE OBJECT中。通常,通过派生VALUE OBJECT而不是改变现有状态,或者将整个责任转移到VALUE OBJECT中,可以完全消除副作用。
VALUE OBJECTS are immutable, which implies that, apart from initializers called only during creation, all their operations are functions. VALUE OBJECTS, like functions, are safer to use and easier to test. An operation that mixes logic or calculations with state change should be refactored into two separate operations (Fowler 1999, p. 279). But by definition, this segregation of side effects into simple command methods only applies to ENTITIES. After completing the refactoring to separate modification from querying, consider a second refactoring to move the responsibility for the complex calculations into a VALUE OBJECT. The side effect often can be completely eliminated by deriving a VALUE OBJECT instead of changing existing state, or by moving the entire responsibility into a VALUE OBJECT.
所以:
Therefore:
将尽可能多的程序逻辑放入函数中,这些函数和操作返回的结果没有明显的副作用。将命令(导致可观察状态修改的方法)严格隔离为不返回域信息的非常简单的操作。当出现适合职责的概念时,通过将复杂逻辑移入值对象来进一步控制副作用。
Place as much of the logic of the program as possible into functions, operations that return results with no observable side effects. Strictly segregate commands (methods that result in modifications to observable state) into very simple operations that do not return domain information. Further control side effects by moving complex logic into VALUE OBJECTS when a concept fitting the responsibility presents itself.
无副作用的函数,尤其是在不可变值对象中,允许安全地组合操作。当函数通过意图揭示接口呈现时,开发人员无需了解其实现细节即可使用它。
SIDE-EFFECT-FREE FUNCTIONS, especially in immutable VALUE OBJECTS, allow safe combination of operations. When a FUNCTION is presented through an INTENTION-REVEALING INTERFACE, a developer can use it without understanding the detail of its implementation.
油漆店的程序可以向顾客展示混合标准油漆的结果。接着上一个示例,这里是单个域类。
A program for paint stores can show a customer the result of mixing standard paints. Picking up where we left off in the last example, here is the single domain class.
图 10.4
Figure 10.4
public void mixIn(Paint other) {
volume = volume.plus(other.getVolume());
// 多行复杂的颜色混合逻辑
// 以分配新的红色、蓝色
// 和黄色值结束。
}
public void mixIn(Paint other) {
volume = volume.plus(other.getVolume());
// Many lines of complicated color-mixing logic
// ending with the assignment of new red, blue,
// and yellow values.
}
mixIn()图 10.5. 该方法的副作用
Figure 10.5. The side effects of the mixIn() method
该方法中发生了很多事情mixIn(),但这种设计确实遵循了将修改与查询分开的规则。我们稍后会讨论的一个问题是,paint 2对象的体积,方法的参数mixIn()一直处于悬而未决的状态。Paint 2 的体积没有因该操作而改变,这在该概念模型的上下文中似乎不太合逻辑。这对原始开发人员来说不是问题,因为据我们所知,他们对操作后的 paint 2 对象不感兴趣,但很难预测副作用或副作用缺失的后果。我们将在讨论ASSERTIONS时很快回到这个问题。现在,让我们看看颜色。
A lot is happening in the mixIn() method, but this design does follow the rule of separating modification from querying. One concern, which we’ll take up later, is that the volume of the paint 2 object, the argument of the mixIn() method, has been left in limbo. Paint 2’s volume is unchanged by the operation, which doesn’t seem quite logical in the context of this conceptual model. This was not a problem for the original developers because, as near as we can tell, they had no interest in the paint 2 object after the operation, but it is hard to anticipate the consequences of side effects or their absence. We’ll return to this question soon in the discussion of ASSERTIONS. For now, let’s look at color.
颜色是这个领域的一个重要概念。让我们尝试将其变成一个明确的对象。它应该叫什么名字?首先想到的是“颜色”,但之前的知识分析已经得出了一个重要的见解,即油漆的混色与更熟悉的 RGB 光显示不同。名称需要反映这一点。
Color is an important concept in this domain. Let’s try the experiment of making it an explicit object. What should it be called? “Color” comes to mind first, but earlier knowledge crunching had already yielded the important insight that color mixing is different for paint than it is for the more familiar RGB light display. The name needs to reflect this.
图 10.6
Figure 10.6
分解出颜料颜色确实比早期版本传达了更多信息,但计算是相同的,仍然在方法中mixIn()。当我们移出颜色数据时,我们应该采取相关的行为。在我们这样做之前,请注意颜料颜色是一个值对象。因此,它应该被视为不可变的。当我们混合颜料时,Paint对象本身就发生了变化。它是一个具有持续生命故事的实体。相反,代表特定黄色色调的颜料颜色始终是黄色。相反,混合将产生一个代表新颜色的新颜料颜色对象。
Factoring out Pigment Color does communicate more than the earlier version, but the computation is the same, still in the mixIn() method. When we moved out the color data, we should have taken related behavior with it. Before we do, note that Pigment Color is a VALUE OBJECT. Therefore, it should be treated as immutable. When we mixed paint, the Paint object itself was changed. It was an ENTITY with an ongoing life story. In contrast, a Pigment Color representing a particular shade of yellow is always exactly that. Instead, mixing will result in a new Pigment Color object representing the new color.
图 10.7
Figure 10.7
public class PigmentColor {
public PigmentColor combinedWith(PigmentColor other,
double ratio) {
// 多行复杂的颜色混合逻辑
// 以创建一个新的 PigmentColor 对象
// 并带有适当的新红色、蓝色和黄色值结束。
}
}
public class Paint {
public void mixIn(Paint other) {
volume = volume + other.getVolume();
double ratio = other.getVolume() / volume;
pigColor =
pigColor.mixedWith(other.pigmentColor(), ratio);
}
}
public class PigmentColor {
public PigmentColor mixedWith(PigmentColor other,
double ratio) {
// Many lines of complicated color-mixing logic
// ending with the creation of a new PigmentColor object
// with appropriate new red, blue, and yellow values.
}
}
public class Paint {
public void mixIn(Paint other) {
volume = volume + other.getVolume();
double ratio = other.getVolume() / volume;
pigmentColor =
pigmentColor.mixedWith(other.pigmentColor(), ratio);
}
}
图 10.8
Figure 10.8
现在Paint中的修改代码尽可能简单。新的Pigment Color类捕获知识并明确传达,它提供了一个无副作用的函数,其结果易于理解、易于测试,并且可以安全地使用或与其他操作结合使用。由于它非常安全,因此真正封装了复杂的颜色混合逻辑。使用此类的开发人员不必了解实现。
Now the modification code in Paint is as simple as possible. The new Pigment Color class captures knowledge and communicates it explicitly, and it provides a SIDE-EFFECT-FREE FUNCTION whose result is easy to understand, easy to test, and safe to use or combine with other operations. Because it is so safe, the complex logic of color mixing is truly encapsulated. Developers using this class don’t have to understand the implementation.
将复杂的计算分解为无副作用的函数可以缩小问题的范围,但实体上仍有一些命令会产生副作用,任何使用它们的人都必须了解它们的后果。SSERTIONS使副作用变得明确且更易于处理。
Separating complex computations into SIDE-EFFECT-FREE FUNCTIONS cuts the problem down to size, but there is still a residue of commands on the ENTITIES that produce side effects, and anyone using them must understand their consequences. ASSERTIONS make side effects explicit and easier to deal with.
确实,不包含复杂计算的命令可能很容易通过检查来解释。但在较大部分由较小部分构成的设计中,命令可能会调用其他命令。使用高级命令的开发人员必须了解每个底层命令的后果。封装就这么多。而且由于对象接口不限制副作用,实现相同接口的两个子类可能会有不同的副作用。使用它们的开发人员会想知道哪个是哪个,以便预测后果。抽象和多态性就这么多。
True, a command containing no complex computations may be fairly easy to interpret by inspection. But in a design where larger parts are built of smaller ones, a command may invoke other commands. The developer using the high-level command must understand the consequences of each underlying command. So much for encapsulation. And because object interfaces do not restrict side effects, two subclasses that implement the same interface can have different side effects. The developer using them will want to know which is which to anticipate the consequences. So much for abstraction and polymorphism.
当操作的副作用仅由其实现隐式定义时,具有大量委托的设计就会变成因果纠缠。理解程序的唯一方法是通过分支路径跟踪执行。封装的价值就丧失了。跟踪具体执行的必要性打败了抽象。
When the side effects of operations are only defined implicitly by their implementation, designs with a lot of delegation become a tangle of cause and effect. The only way to understand a program is to trace execution through branching paths. The value of encapsulation is lost. The necessity of tracing concrete execution defeats abstraction.
我们需要一种方法来理解设计元素的含义以及执行操作的后果,而无需深入研究其内部结构。揭示意图的接口可以帮我们部分实现这一目标,但是非正式的意图建议总是不够的。“按契约设计”学派更进一步,对类和方法做出“断言”,开发人员保证这些断言是正确的。Meyer 1988对这种风格进行了详细讨论。简而言之,“后置条件”描述了操作的副作用,即调用方法的保证结果。“先决条件”就像契约上的细则,是后置条件保证成立所必须满足的条件。类不变量会在任何操作结束时对对象的状态做出断言。还可以为整个聚合声明不变量,从而严格定义完整性规则。
We need a way of understanding the meaning of a design element and the consequences of executing an operation without delving into its internals. INTENTION-REVEALING INTERFACES carry us part of the way there, but informal suggestions of intentions are not always enough. The “design by contract” school goes the next step, making “assertions” about classes and methods that the developer guarantees will be true. This style is discussed in detail in Meyer 1988. Briefly, “post-conditions” describe the side effects of an operation, the guaranteed outcome of calling a method. “Preconditions” are like the fine print on the contract, the conditions that must be satisfied in order for the post-condition guarantee to hold. Class invariants make assertions about the state of an object at the end of any operation. Invariants can also be declared for entire AGGREGATES, rigorously defining integrity rules.
所有这些断言描述的都是状态,而不是过程,因此更容易分析。类不变量有助于描述类的含义,并通过使对象更可预测来简化客户端开发人员的工作。如果您信任后置条件的保证,则不必担心方法的工作原理。委托的影响应该已经纳入断言中。
All these assertions describe state, not procedures, so they are easier to analyze. Class invariants help characterize the meaning of a class, and simplify the client developer’s job by making the objects more predictable. If you trust the guarantee of a post-condition, you don’t have to worry about how a method works. The effects of delegations should already be incorporated into the assertions.
所以:
Therefore:
陈述操作的后置条件和类和聚合的不变量。如果断言不能直接用你的编程语言编码,请为它们编写自动化单元测试。将它们写入文档或图表中,使其符合项目开发流程的风格。
State post-conditions of operations and invariants of classes and AGGREGATES. If ASSERTIONS cannot be coded directly in your programming language, write automated unit tests for them. Write them into documentation or diagrams where it fits the style of the project’s development process.
寻求具有连贯概念集的模型,这可以帮助开发人员推断出预期的断言,从而加速学习曲线并降低矛盾代码的风险。
Seek models with coherent sets of concepts, which lead a developer to infer the intended ASSERTIONS, accelerating the learning curve and reducing the risk of contradictory code.
尽管许多面向对象语言目前不直接支持断言,但断言仍然是一种强大的设计思维方式。自动化单元测试可以部分弥补语言支持的不足。由于断言都是以状态而不是过程为依据的,因此它们使测试易于编写。测试设置将先决条件落实到位;然后,在执行后,测试将检查后置条件是否成立。
Even though many object-oriented languages don’t currently support ASSERTIONS directly, ASSERTIONS are still a powerful way of thinking about a design. Automated unit tests can partially compensate for the lack of language support. Because ASSERTIONS are all in terms of states, rather than procedures, they make tests easy to write. The test setup puts the preconditions in place; then, after execution, the test checks to see if the post-conditions hold.
明确陈述的不变量以及前置条件和后置条件可让开发人员了解使用操作或对象的后果。理论上,任何不矛盾的断言集都可以起作用。但人类不会只在头脑中编译谓词。他们会推断和插入模型的概念,因此找到对人类有意义且满足应用程序需求的模型非常重要。
Clearly stated invariants and pre- and post-conditions allow a developer to understand the consequences of using an operation or object. Theoretically, any noncontradictory set of assertions would work. But humans don’t just compile predicates in their heads. They will be extrapolating and interpolating the concepts of the model, so it is important to find models that make sense to people as well as satisfying the needs of the application.
回想一下,在前面的例子中,我担心PaintmixIn(Paint)类上的操作参数会出现歧义。
Recall that in the previous example I was concerned about the ambiguity of what happens to the argument of the mixIn(Paint) operation on the Paint class.
图 10.9
Figure 10.9
接收器的体积增加了参数体积的量。根据我们对物理涂料的一般理解,这种混合过程应该会消耗相同量的其他涂料,将其排干至零体积或完全消除。当前的实现不会修改参数,而修改参数无论如何都是一种特别危险的副作用。
The receiver’s volume is increased by the amount of the argument’s volume. Drawing on our general understanding of physical paint, this mixing process should deplete the other paint by the same amount, draining it to zero volume, or eliminating it completely. The current implementation does not modify the argument, and modifying arguments is a particularly risky kind of side effect anyway.
为了有一个坚实的基础,让我们先陈述该方法的后置mixIn()条件:
To start on a solid footing, let’s state the post-condition of the mixIn() method as it is:
后p1.mixIn(p2):
After p1.mixIn(p2):
p1.volume增加了 的数量p2.volume。
p1.volume is increased by amount of p2.volume.
p2.volume保持不变。
p2.volume is unchanged.
问题是,开发人员会犯错误,因为这些属性不符合我们邀请他们思考的概念。直接的解决办法是将另一种颜料的体积改为零。改变一个参数是一种不好的做法,但它很容易而且直观。我们可以陈述一个不变量:
The trouble is, developers are going to make mistakes, because these properties don’t fit the concepts we have invited them to think about. The straightforward fix would be change the volume of the other paint to zero. Changing an argument is a bad practice, but it would be easy and intuitive. We could state an invariant:
混合后油漆的总体积不会发生变化。
Total volume of paint is unchanged by mixing.
但是等一下!当开发人员正在考虑这个选项时,他们发现了一个问题。原来,最初的设计师这样做是有令人信服的理由的。最后,程序会报告已添加的未混合涂料列表。毕竟,这个应用程序的最终目的是帮助用户确定要将哪些涂料放入混合物中。
But wait! While developers were pondering this option, they made a discovery. It turns out that there was a compelling reason the original designers made it this way. At the end, the program reports the list of unmixed paints that were added. After all, the ultimate purpose of this application is to help a user figure out which paints to put into a mixture.
因此,使体积模型在逻辑上保持一致会使其不适合其应用要求。这似乎是一个两难的问题。我们是否只能记录奇怪的后置条件并试图通过良好的沟通来弥补?这个世界上并非所有事物都是直观的,有时这是最好的答案。但在这种情况下,尴尬似乎指向了缺失的概念。让我们寻找一个新的模型。
So, to make the volume model logically consistent would make it unsuitable for its application requirements. There seems to be a dilemma. Are we stuck with documenting the weird post-condition and trying to compensate with good communication? Not everything in this world is intuitive, and sometimes that is the best answer. But in this case, the awkwardness seems to point to missing concepts. Let’s look for a new model.
在我们寻找更好的模型时,我们比原来的设计者具有显著的优势,因为在此期间我们进行了知识挖掘和重构,获得了更深入的见解。例如,我们使用VALUE OBJECT上的无副作用函数来计算颜色。这意味着我们可以在需要时随时重复计算。我们应该利用这一点。
As we search for a better model, we have significant advantages over the original designers, because of the knowledge crunching and refactoring to deeper insight that has happened in the interim. For example, we compute color using a SIDE-EFFECT-FREE FUNCTION on a VALUE OBJECT. This means we can repeat the calculation any time we need to. We should take advantage of that.
我们似乎赋予了Paint两种不同的基本职责。让我们尝试拆分它们。
We seem to be giving Paint two different basic responsibilities. Let’s try splitting them.
现在只有一个命令,mixIn()。它只是将一个对象添加到集合中,从直观理解模型可以看出这一效果。所有其他操作都是无副作用函数。
Now there is only one command, mixIn(). It just adds an object to a collection, an effect apparent from an intuitive understanding of the model. All other operations are SIDE-EFFECT-FREE FUNCTIONS.
确认图 10.10中列出的断言之一的测试方法可能看起来像这样(使用 JUnit 测试框架):
A test method confirming one of the ASSERTIONS listed in Figure 10.10 could look something like this (using the JUnit test framework):
public void testMixingVolume {
PigmentColor yellow = new PigmentColor(0, 50, 0);
PigmentColor blue = new PigmentColor(0, 0, 50);
StockPaint paint1 = new StockPaint(1.0, yellow);
StockPaint paint2 = new StockPaint(1.5, blue);
MixedPaint mix = new MixedPaint();
mix.mixIn(paint1);
mix.mixIn(paint2);
assertEquals(2.5, mix.getVolume(), 0.01);
}
public void testMixingVolume {
PigmentColor yellow = new PigmentColor(0, 50, 0);
PigmentColor blue = new PigmentColor(0, 0, 50);
StockPaint paint1 = new StockPaint(1.0, yellow);
StockPaint paint2 = new StockPaint(1.5, blue);
MixedPaint mix = new MixedPaint();
mix.mixIn(paint1);
mix.mixIn(paint2);
assertEquals(2.5, mix.getVolume(), 0.01);
}
图 10.10
Figure 10.10
该模型捕获并传达了更多的领域信息。不变量和后置条件符合常识,这将使它们更易于维护和使用。
This model captures and communicates more of the domain. The invariants and post-conditions make common sense, which will make them easier to maintain and use.
意图揭示接口的通信性,与无副作用函数和断言所提供的可预测性相结合,应该可以使封装和抽象变得安全。
The communicativeness of the INTENTION-REVEALING INTERFACES, combined with the predictability given by SIDE-EFFECT-FREE FUNCTIONS and ASSERTIONS, should make encapsulation and abstraction safe.
可重组元素的下一个成分是有效分解……
The next ingredient in recombinable elements is effective decomposition. . . .
有时人们会将功能细分,以便灵活组合。有时他们会将其大体整合,以封装复杂性。有时他们会寻求一致的粒度,使所有类和操作都达到类似的规模。这些是过度简化,不适合作为一般规则。但他们的动机是解决一组基本问题。
Sometimes people chop functionality fine to allow flexible combination. Sometimes they lump it large to encapsulate complexity. Sometimes they seek a consistent granularity, making all classes and operations to a similar scale. These are oversimplifications that don’t work well as general rules. But they are motivated by a basic set of problems.
当模型或设计的元素嵌入到整体结构中时,它们的功能就会重复。外部界面不会说明客户可能关心的所有内容。它们的含义很难理解,因为不同的概念混杂在一起。
When elements of a model or design are embedded in a monolithic construct, their functionality gets duplicated. The external interface doesn’t say everything a client might care about. Their meaning is hard to understand, because different concepts are mixed together.
另一方面,分解类和方法会毫无意义地使客户端复杂化,迫使客户端对象理解微小的碎片是如何组合在一起的。更糟糕的是,一个概念可能会完全消失。铀原子的一半不是铀。当然,重要的不仅仅是晶粒大小,还有晶粒的运行位置。
On the other hand, breaking down classes and methods can pointlessly complicate the client, forcing client objects to understand how tiny pieces fit together. Worse, a concept can be lost completely. Half of a uranium atom is not uranium. And of course, it isn’t just grain size that counts, but just where the grain runs.
菜谱规则不管用。但大多数领域都存在着深层的逻辑一致性,否则它们在自己的领域中就不可行。这并不是说领域是完全一致的,当然人们谈论它们的方式也不一致。但在某些地方还是有韵律和理由的,否则建模就毫无意义了。由于这种潜在的一致性,当我们找到一个与领域某些部分产生共鸣的模型时,它更有可能与我们后来发现的其他部分保持一致。有时,模型很难适应新的发现,在这种情况下,我们会重构以更深入的洞察力,并希望符合下一个发现。
Cookbook rules don’t work. But there is a logical consistency deep in most domains, or else they would not be viable in their own sphere. This is not to say that domains are perfectly consistent, and certainly the ways people talk about them are not consistent. But there is rhyme and reason somewhere, or else modeling would be pointless. Because of this underlying consistency, when we find a model that resonates with some part of the domain, it is more likely to be consistent with other parts that we discover later. Sometimes the new discovery isn’t easy for the model to adapt to, in which case we refactor to deeper insight, and hope to conform to the next discovery.
这就是为什么反复重构最终会带来灵活性的原因之一。当代码适应新理解的概念或要求时,概念轮廓就会出现。
This is one reason why repeated refactoring eventually leads to suppleness. The CONCEPTUAL CONTOURS emerge as the code is adapted to newly understood concepts or requirements.
高内聚和低耦合这两个基本原则在各个规模的设计中都发挥着作用,从单个方法到类和模块,再到大型结构(参见第 16 章)。这两个原则既适用于概念,也适用于代码。为了避免陷入机械的观点,请通过经常接触你对领域的直觉来磨练你的技术思维。在做每个决定时,问问自己,“这是基于在当前模型和代码中的一组特定关系上,还是它反映了底层领域的某些轮廓?”
The twin fundamentals of high cohesion and low coupling play a role in design at all scales, from individual methods up through classes and MODULES to large-scale structures (see Chapter 16). These two principles apply to concepts as much as to code. To avoid slipping into a mechanistic view of them, temper your technical thinking by frequently touching base with your intuition for the domain. With each decision, ask yourself, “Is this an expedient based on a particular set of relationships in the current model and code, or does it echo some contour of the underlying domain?”
找到概念上有意义的功能单元,最终的设计将既灵活又易于理解。例如,如果两个对象的“相加”在领域中具有连贯的含义,则在该级别实现方法。不要将其add()分为两个步骤。不要在同一操作中继续进行下一步。在稍大的范围内,每个对象都应该是一个完整的概念,一个“整体值” 。1
Find the conceptually meaningful unit of functionality, and the resulting design will be both flexible and understandable. For example, if an “addition” of two objects has a coherent meaning in the domain, then implement methods at that level. Don’t break the add() into two steps. Don’t proceed to the next step within the same operation. On a slightly larger scale, each object should be a single complete concept, a “WHOLE VALUE.”1
同样,任何领域中都有一些领域,软件服务的用户对细节并不感兴趣。我们假设的油漆混合应用程序的用户不会添加红色颜料或蓝色颜料;他们会混合包含所有三种颜料的完整油漆。将不需要剖析或重新排列的东西聚集在一起可以避免混乱,并更容易看到真正需要重新组合的元素。如果我们用户的物理设备允许添加单个颜料,那么该领域就会改变,单个颜料可能会被操纵。油漆化学家需要更精细的控制,这将涉及另一项分析,可能产生比我们用于油漆混合的抽象颜料颜色更详细的油漆组成模型。但这与参与油漆混合应用项目的任何人来说都无关紧要。
By the same token, there are areas in any domain where detail isn’t interesting to the kind of people the software serves. The users of our hypothetical paint mixing application don’t add red pigment or blue pigment; they combine complete paints, which contain all three pigments. Clumping things that don’t need to be dissected or rearranged avoids clutter and makes it easier to see the elements that really are meant to recombine. If our users’ physical equipment allowed individual pigments to be added, the domain would be altered, and the individual pigments might be manipulated. A paint chemist would need still finer control, which would involve a whole other analysis, probably producing a much more detailed model of the makeup of paint than our abstracted pigment color that serves paint mixing. But it is simply irrelevant to anyone involved in the paint mixing application project.
所以:
Therefore:
将设计元素(操作、接口、类和聚合)分解为内聚单元,同时考虑到您对领域中重要划分的直觉。通过连续重构观察变化和稳定性的轴,并寻找解释这些剪切模式的底层概念轮廓。将模型与领域的一致方面对齐,使其成为可行的知识领域。
Decompose design elements (operations, interfaces, classes, and AGGREGATES) into cohesive units, taking into consideration your intuition of the important divisions in the domain. Observe the axes of change and stability through successive refactorings and look for the underlying CONCEPTUAL CONTOURS that explain these shearing patterns. Align the model with the consistent aspects of the domain that make it a viable area of knowledge in the first place.
目标是一组简单的接口,这些接口在UBIQUITOUS LANGUAGE中以逻辑方式组合以做出合理的语句,并且不会因无关选项而分散注意力并带来维护负担。这通常是重构的结果:很难制作但它也许永远不会从面向技术的重构中浮现出来;它从面向更深层次洞察的重构中浮现出来。
The goal is a simple set of interfaces that combine logically to make sensible statements in the UBIQUITOUS LANGUAGE, and without the distraction and maintenance burden of irrelevant options. This is typically an outcome of refactoring: it’s hard to produce up front. But it may never emerge from technically oriented refactoring; it emerges from refactoring toward deeper insight.
即使设计遵循概念轮廓,也需要进行修改和重构。当连续重构趋向于局部化,而不会动摇模型的多个广泛概念时,这表明模型很合适。遇到迫使对对象和方法的细分进行大量更改的需求,这是一个信息:我们对领域的理解需要改进。它提供了一个深化模型并使设计更加灵活的机会。
Even when the design follows CONCEPTUAL CONTOURS, there will need to be modifications and refactoring. When successive refactoring tends to be localized, not shaking multiple broad concepts of the model, it is an indicator of model fit. Encountering a requirement that forces extensive changes in the breakdown of the objects and methods is a message: Our understanding of the domain needs refinement. It presents an opportunity to deepen the model and make the design more supple.
在第 9 章中,我们基于对会计概念的更深入了解重构了贷款跟踪系统:
In Chapter 9, a loan tracking system was refactored based on deeper insight into accounting concepts:
图 10.11
Figure 10.11
新模型只不过比旧模型多了一个对象,但是职责划分却发生了很大的改变。
The new model contained only one more object than the old one, yet the partitioning of responsibility had been greatly changed.
费用表由计算器类中的案例逻辑计算得出,现在被分解为不同类型的费用和利息的独立类。另一方面,之前分开的费用和利息支付被集中在一起。
Schedules, which had been worked out through case logic in the Calculator classes, were exploded into discrete classes for different types of fees and interest. On the other hand, payments of fees and interest, previously kept separate, were lumped together.
由于新明确的概念的共鸣和应计计划层次结构的凝聚力,开发人员认为该模型更好地遵循了该领域的某些概念轮廓。
Because of the resonance of the newly explicit concepts and the cohesiveness of the Accrual Schedule hierarchy, the developer believed that this model better follows some of the domain’s CONCEPTUAL CONTOURS.
图 10.12.此模型可适应添加新类型的应计计划。
Figure 10.12. This model accommodates adding new kinds of Accrual Schedules.
开发人员可以自信地预测的一个变化是增加新的应计计划。这些要求已经迫在眉睫。因此,除了使现有功能更清晰、更简单之外,她还选择了一种可以轻松引入新计划的模型。但她是否找到了一个概念轮廓,可以帮助领域设计随着应用程序和业务的发展而改变和发展?设计将如何处理意外变化,这一点无法保证,但她认为这提高了成功的可能性。
The one change the developer could confidently predict was the addition of new Accrual Schedules. Those requirements were already waiting in the wings. So in addition to making existing functionality clearer and simpler, she chose a model that would make it easy to introduce new schedules. But had she found a CONCEPTUAL CONTOUR that will help the domain design change and grow as the application and the business evolve? There can be no guarantees about how a design will handle unanticipated change, but she thought it had improved the odds.
随着项目的进展,出现了处理提前和延期付款的详细规则的需求。在研究这个问题时,开发人员很高兴地发现,利息支付和费用支付几乎适用相同的规则。这意味着新的模型元素将自然地连接到单个付款类。
As the project proceeded, a requirement emerged for detailed rules for handling early and late payments. As she studied the problem, the developer was pleased to see that virtually the same rules applied to payments on interest and to payments on fees. This meant that the new model elements would connect naturally to the single Payment class.
图 10.13
Figure 10.13
旧设计会强制两个Payment History类之间出现重复。(这种困难可能会引发这样的认识:Payment类应该共享,从而导致另一条通往类似模型的路径。)这种易于扩展的情况并不是因为她预见到了这种变化。也不是因为她设计了一个如此通用的设计,可以适应任何可以想象到的变化。之所以能实现这种扩展,是因为在之前的重构中,设计与领域的基本概念保持一致。
The old design would have forced duplication between the two Payment History classes. (This difficulty might have triggered an insight that the Payment class should be shared, leading by another path to a similar model.) This ease of extension did not come because she anticipated the change. Nor did it come because she made a design so versatile it could accommodate any conceivable change. It happened because in the previous refactoring, the design was aligned with underlying concepts of the domain.
意图显示接口允许客户端将对象呈现为意义单元,而不仅仅是机制。无副作用函数和断言使使用这些单元和进行复杂组合变得安全。概念轮廓的出现使模型的各个部分稳定下来,也使单元的使用和组合更加直观。
INTENTION-REVEALING INTERFACES allow clients to present objects as units of meaning rather than just mechanisms. SIDE-EFFECT-FREE FUNCTIONS and ASSERTIONS make it safe to use those units and make complex combinations. The emergence of CONCEPTUAL CONTOURS stabilizes parts of the model and also makes the units more intuitive to use and combine.
当相互依赖关系迫使我们同时思考太多事情时,我们仍然会陷入概念超载的境地。……
We can still run into conceptual overload when interdependencies force us to think about too many of these things at a time. . . .
相互依赖性使得模型和设计难以理解。它们也使得它们难以测试和维护。而且相互依赖性很容易堆积起来。
Interdependencies make models and designs hard to understand. They also make them hard to test and maintain. And interdependencies pile up easily.
当然,每个关联都是依赖项,理解类需要理解它所依附的东西。这些依附的东西会依附到更多的东西上,因此也必须理解它们。每个方法的每个参数的类型也是一种依赖项。每个返回值也是如此。
Every association is, of course, a dependency, and understanding a class requires understanding what it is attached to. Those attached things will be attached to still more things, and they have to be understood too. The type of every argument of every method is also a dependency. So is every return value.
如果存在一个依赖项,则必须同时考虑两个类以及它们之间的关系性质。如果存在两个依赖项,则必须考虑三个类中的每一个类、类与每个类之间的关系性质以及它们之间可能存在的任何关系。如果它们又存在依赖项,则还必须小心这些依赖项。如果存在三个依赖项... 问题就会像滚雪球一样越滚越大。
With one dependency, you have to think about two classes at the same time, and the nature of their relationship. With two dependencies, you have to think about each of the three classes, the nature of the class’s relationship to each of them, and any relationship they might have to each other. If they in turn have dependencies, you have to be wary of those also. With three dependencies . . . it snowballs.
MODULES和AGGREGATES都旨在限制相互依赖关系。当将高度内聚的子域划分为 MODULE 时,一组对象将与系统的其余部分分离,因此相互关联的概念数量是有限的。但是,即使是 MODULE 也有很多事情需要考虑,除非你近乎狂热地致力于控制其中的依赖关系。
Both MODULES and AGGREGATES are aimed at limiting the web of interdependencies. When a highly cohesive subdomain is carved out into a MODULE, a set of objects are decoupled from the rest of the system, so there are a finite number of interrelated concepts. But even a MODULE can be a lot to think about without an almost fanatical commitment to controlling dependencies within it.
即使在MODULE中,随着依赖项的增加,解释设计的难度也会大大增加。这会增加脑力负担,限制开发人员可以处理的设计复杂性。隐式概念比显式引用更能增加这种负担。
Even within a MODULE, the difficulty of interpreting a design increases wildly as dependencies are added. This adds to mental overload, limiting the design complexity a developer can handle. Implicit concepts contribute to this load even more than explicit references.
精炼模型经过提炼,直到概念之间剩余的每一个联系都代表了这些概念含义的基本内容。在一个重要的子集中,依赖项的数量可以减少到零,从而产生一个可以完全理解的类,以及一些原语和基本的库概念。
Refined models are distilled until every remaining connection between concepts represents something fundamental to the meaning of those concepts. In an important subset, the number of dependencies can be reduced to zero, resulting in a class that can be fully understood all by itself, along with a few primitives and basic library concepts.
在每个编程环境中,一些基本知识都无处不在,因此它们总是被牢记在心。例如,在 Java 开发中,原语和一些标准库提供了数字、字符串和集合等基础知识。实际上,“整数”不会增加智力负担。除此之外,每个额外的概念为了理解一个物体而需要在脑海中思考,这会导致精神负担过重。
In every programming environment, a few basics are so pervasive that they are always in mind. For example, in Java development, primitives and a few standard libraries provide basics like numbers, strings, and collections. Practically speaking, “integers” don’t add to the intellectual load. Beyond that, every additional concept that has to be held in mind in order to understand an object contributes to mental overload.
隐式概念(无论是否被识别)与显式引用一样重要。虽然我们通常可以忽略对整数和字符串等原始值的依赖,但我们不能忽略它们所代表的内容。例如,在第一个颜料混合示例中,Paint对象保存了三个公共整数,分别表示红色、黄色和蓝色值。创建Pigment Color对象并没有增加所涉及的概念或依赖项的数量。它确实使已经存在的概念更加明确和易于理解。另一方面,Collection size()操作返回的int只是一个计数,即整数的基本含义,因此没有暗含任何新概念。
Implicit concepts, recognized or unrecognized, count just as much as explicit references. Although we can generally ignore dependencies on primitive values such as integers and strings, we can’t ignore what they represent. For example, in the first paint mixing examples, the Paint object held three public integers representing red, yellow, and blue color values. The creation of the Pigment Color object did not increase the number of concepts involved or the dependencies. It did make the ones that were already there more explicit and easier to understand. On the other hand, the Collection size() operation returns an int that is simply a count, the basic meaning of an integer, so no new concept is implied.
除非证明依赖关系是对象背后概念的基础,否则每个依赖关系都是可疑的。这种审查从分解模型概念本身开始。然后需要关注每个单独的关联和操作。模型和设计选择可以逐渐消除依赖关系——通常可以消除到零。
Every dependency is suspect until proven basic to the concept behind the object. This scrutiny starts with the factoring of the model concepts themselves. Then it requires attention to each individual association and operation. Model and design choices can chip away at dependencies—often to zero.
低耦合是对象设计的基础。如果可以,就全力以赴。从图中消除 所有 其他概念。然后该类将完全独立,可以单独研究和理解。每个这样的独立类都会大大减轻理解 MODULE 的负担。
Low coupling is fundamental to object design. When you can, go all the way. Eliminate all other concepts from the picture. Then the class will be completely self-contained and can be studied and understood alone. Every such self-contained class significantly eases the burden of understanding a MODULE.
对同一模块内其他类的依赖比对外部类的依赖危害小。同样,当两个对象自然紧密耦合时,涉及同一对对象的多个操作实际上可以明确关系的性质。目标不是消除所有依赖,而是消除所有不必要的依赖。如果无法消除每个依赖,那么删除每个依赖都会让开发人员将精力集中在剩余的概念依赖上。
Dependencies on other classes within the same module are less harmful than those outside. Likewise, when two objects are naturally tightly coupled, multiple operations involving the same pair can actually clarify the nature of the relationship. The goal is not to eliminate all dependencies, but to eliminate all nonessential ones. If every dependency can’t be eliminated, each one that is removed frees the developer to concentrate on the remaining conceptual dependencies.
尝试将最复杂的计算分解到独立类 (STANDALONE CLASSES)中,或许可以通过对更多连接类所持有的值对象 (VALUE OBJECTS)进行建模来实现。
Try to factor the most intricate computations into STANDALONE CLASSES, perhaps by modeling VALUE OBJECTS held by the more connected classes.
油漆的概念与颜色的概念有着根本的联系。但是,颜色,甚至是颜料的颜色,都可以在不考虑油漆的情况下考虑。通过明确这两个概念并提炼关系,剩下的单向关联说明了重要的事情,颜料颜色 类,其中大部分计算复杂性在于,可以单独研究和测试。
The concept of paint is fundamentally related to the concept of color. But color, even of pigment, can be considered without paint. By making these two concepts explicit and distilling the relationship, the remaining one-way association says something important, and the Pigment Color class, where most of the computational complexity lies, can be studied and tested alone.
低耦合是减少概念负担的基本方法。STANDALONE CLASS是低耦合的极致。
Low coupling is a basic way to reduce conceptual overload. A STANDALONE CLASS is an extreme of low coupling.
消除依赖关系不应意味着通过任意将所有内容简化为基本内容来降低模型的复杂度。本章的最后一个模式“操作闭包”是减少依赖关系同时保持丰富界面的技术示例。...
Eliminating dependencies should not mean dumbing down the model by arbitrarily reducing everything to primitives. The final pattern of this chapter, CLOSURE OF OPERATIONS, is an example of a technique for reducing dependency while keeping a rich interface. . . .
如果我们将两个实数相乘,就会得到另一个实数。[实数是所有有理数和所有无理数。] 因为这始终是正确的,所以我们说实数“在乘法运算下是封闭的”:没有办法逃离集合。当你将集合中的任意两个元素组合起来时,结果也包含在集合中。
If we take two real numbers and multiply them together, we get another real number. [The real numbers are all the rational numbers and all the irrational numbers.] Because this is always true, we say that the real numbers are “closed under the operation of multiplication”: there is no way to escape the set. When you combine any two elements of the set, the result is also included in the set.
—德雷塞尔大学数学论坛
—The Math Forum, Drexel University
当然,依赖关系是存在的,当依赖关系是概念的基础时,这并不是坏事。剥离接口以处理原语可能会使它们变得贫乏。但是,许多不必要的依赖关系,甚至整个概念,都是在接口处引入的。
Of course, there will be dependencies, and that isn’t a bad thing when the dependency is fundamental to the concept. Stripping interfaces down to deal with nothing but primitives can impoverish them. But a lot of unnecessary dependencies, and even entire concepts, get introduced at interfaces.
大多数有趣的物体最终都会做一些无法仅用原语来表征的事情。
Most interesting objects end up doing things that can’t be characterized by primitives alone.
精细化设计中的另一种常见做法是我所说的“操作闭包”。这个名字来自最精细的概念系统——数学。1 + 1 = 2。加法运算在实数集下是封闭的。数学家热衷于不引入无关的概念,而闭包性质为他们提供了一种定义运算而不涉及任何其他概念的方法。我们已经习惯了数学的精细化,以至于很难理解它的小技巧有多么强大。但这一技巧在软件设计中也被广泛使用。XSLT 的基本用途是将一个 XML 文档转换为另一个 XML 文档。这种 XSLT 操作在 XML 文档集下是封闭的。闭包性质极大地简化了操作的解释,并且很容易考虑将封闭的操作链接在一起或组合起来。
Another common practice in refined designs is what I call “CLOSURE OF OPERATIONS.” The name comes from that most refined of conceptual systems, mathematics. 1 + 1 = 2. The addition operation is closed under the set of real numbers. Mathematicians are fanatical about not introducing extraneous concepts, and the property of closure provides them a way of defining an operation without involving any other concepts. We are so accustomed to the refinement of mathematics that it can be hard to grasp how powerful its little tricks are. But this one is used extensively in software designs as well. The basic use of XSLT is to transform one XML document into another XML document. This sort of XSLT operation is closed under the set of XML documents. The property of closure tremendously simplifies the interpretation of an operation, and it is easy to think about chaining together or combining closed operations.
所以:
Therefore:
在合适的情况下,定义一个操作,其返回类型与其参数的类型相同。如果实现者具有用于计算的状态,则实现者实际上是操作的参数,因此参数和返回值应与实现者的类型相同。这样的操作在该类型的实例集下是封闭的。封闭的操作提供了高级接口,而不会引入对其他概念的任何依赖。
Where it fits, define an operation whose return type is the same as the type of its argument(s). If the implementer has state that is used in the computation, then the implementer is effectively an argument of the operation, so the argument(s) and return value should be of the same type as the implementer. Such an operation is closed under the set of instances of that type. A closed operation provides a high-level interface without introducing any dependency on other concepts.
此模式最常应用于VALUE OBJECT的操作。由于ENTITY的生命周期在领域中具有重要意义,因此您不能只是凭空想象出一个新的 ENTITY 来回答问题。有些操作在ENTITY类型下是封闭的。您可以向Employee对象询问其主管并返回另一个Employee。但一般来说,ENTITIES不是那种可能成为计算结果的概念。因此,在大多数情况下,这是一个在VALUE OBJECTS中寻找的机会。
This pattern is most often applied to the operations of a VALUE OBJECT. Because the life cycle of an ENTITY has significance in the domain, you can’t just conjure up a new one to answer a question. There are operations that are closed under an ENTITY type. You could ask an Employee object for its supervisor and get back another Employee. But in general, ENTITIES are not the sort of concepts that are likely to be the result of a computation. So, for the most part, this is an opportunity to look for in the VALUE OBJECTS.
运算可以在抽象类型下封闭,在这种情况下,特定参数可以属于不同的具体类。毕竟,加法在实数下封闭,实数可以是有理数,也可以是无理数。
An operation can be closed under an abstract type, in which case specific arguments can be of different concrete classes. After all, addition is closed under real numbers, which can be either rational or irrational.
当你在试验、寻找减少相互依赖和增加内聚力的方法时,你有时会陷入这种模式。参数与实现者匹配,但返回类型不同,或者返回类型与接收者匹配,而参数不同。这些操作不是封闭的,但它们确实提供了CLOSURE的一些优点。当额外的类型是原始或基本库类时,它几乎和CLOSURE一样解放你的思维。
As you’re experimenting, looking for ways to reduce interdependence and increase cohesion, you sometimes get halfway to this pattern. The argument matches the implementer, but the return type is different, or the return type matches the receiver and the argument is different. These operations are not closed, but they do give some of the advantages of CLOSURE. When the extra type is a primitive or basic library class, it frees the mind almost as much as CLOSURE.
在前面的例子中,颜料颜色操作在 颜料颜色mixedWith()下关闭,书中还散布着其他几个例子。下面这个例子展示了这个想法是多么有用,即使没有达到真正的关闭。
In the earlier example, the Pigment Color mixedWith() operation was closed under Pigment Colors, and there are several other examples scattered through the book. Here’s an example that shows how useful this idea can be, even when true CLOSURE isn’t reached.
在 Java 中,如果要从Collection中选择一个元素子集,可以请求Iterator。然后迭代元素,测试每个元素,并可能将匹配项累积到新的Collection中。
In Java, if you want to select a subset of elements from a Collection, you request an Iterator. Then you iterate through the elements, testing each one, probably accumulating the matches into a new Collection.
设置员工 = (一些员工对象集合);
设置 lowPaidEmployees = new HashSet();
迭代器 it = employees.iterator();
while (it.hasNext()) {
员工 anEmployee = it.next();
如果 (anEmployee.salary() < 40000)
lowPaidEmployees.add(anEmployee);
}
Set employees = (some Set of Employee objects);
Set lowPaidEmployees = new HashSet();
Iterator it = employees.iterator();
while (it.hasNext()) {
Employee anEmployee = it.next();
if (anEmployee.salary() < 40000)
lowPaidEmployees.add(anEmployee);
}
从概念上讲,我选择了一个集合的子集。我需要这个额外的概念、迭代器及其所有机械复杂性做什么?在 Smalltalk 中,我将在Collection上调用“select”操作,并将测试作为参数传入。返回将是一个仅包含通过测试的元素的新Collection 。
Conceptually, I’ve selected a subset of a set. What do I need with this extra concept, Iterator, and all its mechanical complexity? In Smalltalk, I would call the “select” operation on the Collection, passing in the test as an argument. The return would be a new Collection containing just the elements that passed the test.
员工 := (一些员工对象集合)。lowPaidEmployees
:= 员工选择:
[:anEmployee | anEmployee 薪水 < 40000]。
employees := (some Set of Employee objects).
lowPaidEmployees := employees select:
[:anEmployee | anEmployee salary < 40000].
Smalltalk集合提供了其他这样的函数,它们返回派生的集合,这些集合可以是几个具体类。这些操作不是封闭的,因为它们以“块”为参数。但块是 Smalltalk 中的基本库类型,因此它们不会增加开发人员的脑力负担。由于返回值与实现者相匹配,因此它们可以像一系列过滤器一样串在一起。它们易于编写和阅读。它们不会引入与选择子集问题无关的无关概念。
The Smalltalk Collections provide other such FUNCTIONS that return derived Collections, which can be of several concrete classes. The operations are not closed, because they take a “block” as an argument. But blocks are a basic library type in Smalltalk, so they don’t add to the developer’s mental load. Because the return value matches the implementer, they can be strung together, like a series of filters. They are easy to write and easy to read. They do not introduce extraneous concepts that are irrelevant to the problem of selecting subsets.
本章介绍的模式说明了一般的设计风格和思考设计的方式。使软件显而易见、可预测且具有可沟通性,使抽象和封装变得有效。模型可以分解,这样对象就易于使用和理解,同时仍具有丰富的高级接口。
The patterns presented in this chapter illustrate a general style of design and a way of thinking about design. Making software obvious, predictable, and communicative makes abstraction and encapsulation effective. Models can be factored so that objects are simple to use and understand yet still have rich, high-level interfaces.
这些技术需要相当高超的设计技能才能应用,有时甚至需要编写客户端。模型驱动设计的实用性取决于详细设计和实施决策的质量,只需几个困惑的开发人员就能让项目偏离目标。
These techniques require fairly advanced design skills to apply and sometimes even to write a client. The usefulness of a MODEL-DRIVEN DESIGN is sensitive to the quality of the detailed design and implementation decisions, and it only takes a few confused developers to derail a project from the goal.
也就是说,对于愿意培养建模和设计技能的团队来说,这些模式及其反映的思维方式可以使开发人员开发出可以反复制作的软件,从而创建复杂的软件。
That said, for the team willing to cultivate its modeling and design skills, these patterns and the way of thinking they reflect yield software that developers can work and rework to create complex software.
即使我们采用相对非正式的测试方式, SSERTIONS也可以带来更好的设计。但不能真正保证手写软件。仅举一种逃避断言的方法,代码可能会有未被明确排除的额外副作用。无论我们的设计如何模型驱动,我们最终还是会编写程序来产生概念交互的效果。我们花费大量时间编写样板代码,而这些代码实际上并没有增加任何意义或行为。这很乏味,而且容易出错,而且大部分时间都掩盖了我们模型的意义。(有些语言比其他语言好,但所有语言都要求我们做大量繁重的工作。)本章中的意图揭示接口和其他模式会有所帮助,但它们永远无法为传统的面向对象程序提供形式严谨性。
ASSERTIONS can lead to much better designs, even with our relatively informal way of testing them. But there can be no real guarantees in handwritten software. To name just one way of evading ASSERTIONS, code could have additional side effects that were not specifically excluded. No matter how MODEL-DRIVEN our design is, we still end up writing procedures to produce the effect of the conceptual interactions. And we spend so much of our time writing boilerplate code that doesn’t really add any meaning or behavior. This is tedious and fraught with error, and the bulk of it obscures the meaning of our model. (Some languages are better than others, but all require us to do a lot of grunt work.) INTENTION-REVEALING INTERFACES and the other patterns in this chapter help, but they can never give conventional object-oriented programs formal rigor.
这些是声明式设计背后的一些动机。这个术语对很多人来说意味着很多东西,但通常它表示一种编写程序或程序的某些部分的方式,作为一种可执行规范。属性的非常精确的描述实际上控制着软件。在其各种形式中,这可以通过反射机制或在编译时通过代码生成(根据声明自动生成常规代码)来完成。这种方法允许另一个开发人员按表面价值理解声明。这是绝对的保证。
These are some of the motivations behind declarative design. This term means many things to many people, but usually it indicates a way to write a program, or some part of a program, as a kind of executable specification. A very precise description of properties actually controls the software. In its various forms, this could be done through a reflection mechanism or at compile time through code generation (producing conventional code automatically, based on the declaration). This approach allows another developer to take the declaration at face value. It is an absolute guarantee.
从模型属性声明生成可运行的程序是模型驱动设计的一种圣杯,但在实践中确实存在缺陷。例如,以下是我不止一次遇到的两个具体问题。
Generating a running program from a declaration of model properties is a kind of Holy Grail of MODEL-DRIVEN DESIGN, but it does have its pitfalls in practice. For example, here are just two particular problems I’ve encountered more than once.
• 声明语言的表达能力不足以完成所有需要做的事情,而框架却使软件很难在自动化部分之外进行扩展
• A declaration language not expressive enough to do everything needed, but a framework that makes it very difficult to extend the software beyond the automated portion
• 代码生成技术将生成的代码合并到手写代码中,从而削弱了迭代周期,使得重新生成具有极大的破坏性
• Code-generation techniques that cripple the iterative cycle by merging generated code into handwritten code in a way that makes regeneration very destructive
许多声明式设计尝试的意外后果是模型和应用程序的简化,因为开发人员受制于框架的限制,为了交付某些东西而进行设计分类。
The unintended consequence of many attempts at declarative design is the dumbing-down of the model and application, as developers, trapped by the limitations of the framework, enact design triage in order to get something delivered.
使用推理引擎和规则库的基于规则的编程是另一种有前途的声明式设计方法。不幸的是,细微的问题可能会破坏这一意图。
Rule-based programming with an inference engine and a rule base is another promising approach to declarative design. Unfortunately, subtle issues can undermine this intention.
虽然基于规则的程序在原则上是声明式的,但大多数系统都添加了“控制谓词”,以允许性能调整。此控制代码引入了副作用,因此行为不再完全由声明的规则决定。添加、删除或重新排序规则可能会导致意外的、不正确的结果。因此,逻辑程序员必须小心谨慎,让代码的效果显而易见,就像对象程序员一样。
Although a rules-based program is declarative in principle, most systems have “control predicates” that were added to allow performance tuning. This control code introduces side effects, so that the behavior is no longer dictated completely by the declared rules. Adding, removing, or reordering the rules can cause unexpected, incorrect results. Therefore, a logic programmer has to be careful to keep the effect of code obvious, just as an object programmer does.
如果开发人员有意或无意地绕过声明式方法,许多声明式方法可能会被破坏。当系统难以使用或限制过多时,这种情况很可能会发生。每个人都必须遵守框架的规则才能获得声明式程序的好处。
Many declarative approaches can be corrupted if the developers bypass them intentionally or unintentionally. This is likely when the system is difficult to use or overly restrictive. Everyone has to follow the rules of the framework in order to get the benefits of a declarative program.
我所见过的最大价值是当一个范围狭窄的框架自动执行设计中特别繁琐且容易出错的方面(例如持久性和对象关系映射)时。其中最好的是减轻开发人员的繁重工作负担,同时让他们完全自由地进行设计。
The greatest value I’ve seen delivered has been when a narrowly scoped framework automates a particularly tedious and error-prone aspect of the design, such as persistence and object-relational mapping. The best of these unburden developers of drudge work while leaving them complete freedom to design.
一种有趣的方法有时是声明性的,即领域特定语言。在这种风格中,客户端代码是用针对特定领域的特定模型定制的编程语言编写的。例如,用于运输系统的语言可能包括货物和路线等术语,以及关联它们的语法。然后,程序被编译成一种传统的面向对象语言,其中类库为该语言中的术语提供实现。
An interesting approach that is sometimes declarative is the domain-specific language. In this style, client code is written in a programming language tailored to a particular model of a particular domain. For example, a language for shipping systems might include terms such as cargo and route, along with syntax for associating them. The program is then compiled, often into a conventional object-oriented language, where a library of classes provides implementations for the terms in the language.
在这种语言中,程序可以非常具有表现力,并与UBIQUITOUS LANGUAGE建立最紧密的联系。这是一个令人兴奋的概念,但领域特定语言在我见过的基于面向对象技术的方法中也有其缺点。
In such a language, programs can be extremely expressive, and make the strongest connection with the UBIQUITOUS LANGUAGE. This is an exciting concept, but domain-specific languages also have their drawbacks in the approaches I’ve seen based on object-oriented technology.
为了完善模型,开发人员需要能够修改语言。这可能涉及修改语法声明和其他语言解释功能,以及修改底层类库。我所有这些都有利于学习先进的技术和设计理念,但我们必须清醒地评估特定团队的技能,以及未来维护团队可能具备的技能。此外,用同一种语言实现的应用程序和模型的无缝性也具有价值。另一个缺点是,重构客户端代码以符合修订后的模型及其相关的领域特定语言可能很困难。当然,有人可能会想出一种技术解决方案来解决重构问题。
To refine the model, a developer needs to be able to modify the language. This may involve modifying grammar declarations and other language-interpreting features, as well as modifying underlying class libraries. I’m all in favor of learning advanced technology and design concepts, but we have to soberly assess the skills of a particular team, as well as the likely skills of future maintenance teams. Also, there is value in the seamlessness of an application and a model implemented in the same language. Another drawback is that it can be difficult to refactor client code to conform to a revised model and its associated domain-specific language. Of course, someone may come up with a technical fix for the refactoring problems.
这种技术可能对非常成熟的模型最有用,也许客户端代码是由不同的团队编写的。通常,这样的设置会导致技术含量高的框架构建者与技术不熟练的应用程序构建者之间产生有害的区别,但情况不一定如此。
This technique might be most useful for very mature models, perhaps where client code is being written by a different team. Generally, such setups lead to the poisonous distinction between highly technical framework builders and technically unskilled application builders, but it doesn’t have to be that way.
在方案编程语言中,非常相似的东西是标准编程风格的一部分,这样就可以创建领域特定语言的表达能力,而无需分叉系统。
In the scheme programming language, something very similar is part of standard programming style, so that the expressiveness of a domain-specific language can be created without bifurcating the system.
一旦您的设计具有意图揭示接口、无副作用函数和断言,您就进入了声明式领域。一旦您拥有可组合的元素来传达其含义,并具有特征化或明显的效果,或者根本没有可观察到的效果,就可以获得声明式设计的许多好处。
Once your design has INTENTION-REVEALING INTERFACES, SIDE-EFFECT-FREE FUNCTIONS, and ASSERTIONS, you are edging into declarative territory. Many of the benefits of declarative design are obtained once you have combinable elements that communicate their meaning, and have characterized or obvious effects, or no observable effects at all.
灵活的设计可以让客户端代码使用声明式的设计风格。为了说明这一点,下一节将整合本章中的一些模式,使SPECIFICATION更加灵活和声明式。
A supple design can make it possible for the client code to use a declarative style of design. To illustrate, the next section will bring together some of the patterns in this chapter to make the SPECIFICATION more supple and declarative.
第 9 章介绍了SPECIFICATION的基本概念、它在程序中可以扮演的角色以及实现中涉及的一些内容。现在让我们看一看在规则复杂的情况下非常有用的一些花哨功能。
Chapter 9 covered the basic concept of SPECIFICATION, the roles it can play in a program, and some sense of what is involved in implementation. Now let’s take a look at a few bells and whistles that can be very useful in some situations with complicated rules.
S PECIFICATION是对已建立的形式主义谓词的改编。谓词还有其他有用的属性,我们可以选择性地利用它们。
SPECIFICATION is an adaptation of an established formalism, the predicate. Predicates have other useful properties that we can draw on, selectively.
使用SPECIFICATIONS时,您很快就会遇到想要组合它们的情况。正如刚才提到的,SPECIFICATION是谓词的一个示例,谓词可以通过“AND”、“OR”和“NOT”操作进行组合和修改。这些逻辑操作在谓词下是封闭的,因此SPECIFICATION组合将表现出操作的封闭性。
When using SPECIFICATIONS, you quickly come across situations in which you would like to combine them. As just mentioned, a SPECIFICATION is an example of a predicate, and predicates can be combined and modified with the operations “AND,” “OR,” and “NOT.” These logical operations are closed under predicates, so SPECIFICATION combinations will exhibit CLOSURE OF OPERATIONS.
由于SPECIFICATIONS内置了大量通用功能,因此创建一个可用于各种SPECIFICATIONS的抽象类或接口变得非常有用。这意味着将参数键入为一些高级抽象类。
As significant generalized capability is built into SPECIFICATIONS, it becomes very useful to create an abstract class or interface that can be used for SPECIFICATIONS of all sorts. This means typing arguments as some high-level abstract class.
公共接口规范 {
boolean isSatisfiedBy(Object candidates);
}
public interface Specification {
boolean isSatisfiedBy(Object candidate);
}
此抽象在方法开头调用了保护子句,但除此之外,它不会影响功能。例如,容器规范(来自第 9 章第236页的示例)将按以下方式修改:
This abstraction calls for a guard clause at the beginning of the method, but otherwise it does not affect functionality. For example, the Container Specification (from the example in Chapter 9, on page 236) would be modified this way:
公共类 ContainerSpecification实现规范{
私有 ContainerFeature requiredFeature;
公共 ContainerSpecification(ContainerFeature required){
requiredFeature = required;
}
boolean isSatisfiedBy(Object candidates){
如果(!candidate instanceof Container)返回 false;
返回
(Container) candidate.getFeatures()。contains(requiredFeature);
}
}
public class ContainerSpecification implements Specification {
private ContainerFeature requiredFeature;
public ContainerSpecification(ContainerFeature required) {
requiredFeature = required;
}
boolean isSatisfiedBy(Object candidate){
if (!candidate instanceof Container) return false;
return
(Container)candidate.getFeatures().contains(requiredFeature);
}
}
现在,让我们通过添加三个新操作来扩展Specification接口:
Now, let’s extend the Specification interface by adding the three new operations:
公共接口规范 {
boolean isSatisfiedBy(对象候选);
规范和(规范其他);
规范或(规范其他);
规范不();
}
public interface Specification {
boolean isSatisfiedBy(Object candidate);
Specification and(Specification other);
Specification or(Specification other);
Specification not();
}
回想一下,有些集装箱规格被配置成需要通风集装箱,而另一些则需要装甲集装箱。一种既易挥发又易爆炸的化学品大概需要这两种规格。使用新方法,很容易做到。
Recall that some Container Specifications were configured to require ventilated Containers and others to require armored Containers. A chemical that is both volatile and explosive would, presumably, need both of these SPECIFICATIONS. Easily done, using the new methods.
通风规范 = 新的 ContainerSpecification(VENTILATED);
装甲规范 = 新的 ContainerSpecification(ARMORED);
两者规范 = 通风和(装甲);
Specification ventilated = new ContainerSpecification(VENTILATED);
Specification armored = new ContainerSpecification(ARMORED);
Specification both = ventilated.and(armored);
声明定义了一个具有预期属性的新规范对象。这种组合需要更复杂的容器规范,并且仍然具有特殊用途。
The declaration defines a new Specification object with the expected properties. This combination would have required a more complicated Container Specification, and would still have been special purpose.
假设我们有多种通风容器。对于某些物品来说,将它们装入哪种容器可能并不重要。它们可以放在任何一种容器中。
Suppose we had more than one kind of ventilated Container. It might not matter for some items which kind they were packed into. They could be placed in either type.
规范 ventilatedType1 =
new ContainerSpecification(VENTILATED_TYPE_1);
规范 ventilatedType2 =
new ContainerSpecification(VENTILATED_TYPE_2);
规范 either = ventilatedType1.or(ventilatedType2);
Specification ventilatedType1 =
new ContainerSpecification(VENTILATED_TYPE_1);
Specification ventilatedType2 =
new ContainerSpecification(VENTILATED_TYPE_2);
Specification either = ventilatedType1.or(ventilatedType2);
如果认为将沙子储存在专门的容器中是一种浪费,我们可以通过指定没有特殊功能的“廉价”容器来禁止这种做法。
If it was considered wasteful to store sand in specialized containers, we could prohibit it by SPECIFYING a “cheap” container with no special features.
规格便宜 = (ventilated.not()).and(armored.not());
Specification cheap = (ventilated.not()).and(armored.not());
这种约束可以防止第 9 章中讨论的原型仓库包装机的一些次优行为。
This constraint would have prevented some of the suboptimal behavior of the prototype warehouse packer discussed in Chapter 9.
用简单元素构建复杂规范的能力提高了代码的表现力。组合以声明式风格编写。
The ability to build complex specifications out of simple elements increases the expressiveness of the code. The combinations are written in a declarative style.
根据SPECIFICATIONS的实现方式,这些操作符可能容易提供,也可能难以提供。下面是一个非常简单的实现,在某些情况下效率低下,而在其他情况下却非常实用。它只是一个解释性示例。与任何模式一样,有很多方法可以实现它。
Depending on how SPECIFICATIONS are implemented, these operators may be easy or difficult to provide. What follows is a very simple implementation, which would be inefficient in some situations and quite practical in others. It is meant as an explanatory example. Like any pattern, there are many ways to implement it.
公共抽象类 AbstractSpecification 实现
规范 {
公共规范和(规范其他) {
返回新的 AndSpecification(this,其他);
}
公共规范或(规范其他) {
返回新的 OrSpecification(this,其他);
}
公共规范不() {
返回新的 NotSpecification(this);
}
}
公共类 AndSpecification 扩展 AbstractSpecification {
规范一;
规范其他;
公共 AndSpecification(规范 x,规范 y) {
一个 = x;
其他 = y;
}
公共布尔 isSatisfiedBy(对象候选人) {
返回 一个.isSatisfiedBy(候选人) &&
其他.isSatisfiedBy(候选人);
}
}
公共类 OrSpecification 扩展 AbstractSpecification {
规范一个;
规范其他;
公共 OrSpecification(规范 x,规范 y) {
一个 = x;
其他 = y;
}
公共布尔 isSatisfiedBy(对象候选人) {
返回 一个.isSatisfiedBy(候选人) ||
其他.isSatisfiedBy(候选人);
}
}
公共类 NotSpecification 扩展 AbstractSpecification {
规范包装;
公共 NotSpecification(规范 x) {
wrapped = x;
}
公共布尔 isSatisfiedBy(对象候选人) {
返回 !wrapped.isSatisfiedBy(候选人);
}
}
public abstract class AbstractSpecification implements
Specification {
public Specification and(Specification other) {
return new AndSpecification(this, other);
}
public Specification or(Specification other) {
return new OrSpecification(this, other);
}
public Specification not() {
return new NotSpecification(this);
}
}
public class AndSpecification extends AbstractSpecification {
Specification one;
Specification other;
public AndSpecification(Specification x, Specification y) {
one = x;
other = y;
}
public boolean isSatisfiedBy(Object candidate) {
return one.isSatisfiedBy(candidate) &&
other.isSatisfiedBy(candidate);
}
}
public class OrSpecification extends AbstractSpecification {
Specification one;
Specification other;
public OrSpecification(Specification x, Specification y) {
one = x;
other = y;
}
public boolean isSatisfiedBy(Object candidate) {
return one.isSatisfiedBy(candidate) ||
other.isSatisfiedBy(candidate);
}
}
public class NotSpecification extends AbstractSpecification {
Specification wrapped;
public NotSpecification(Specification x) {
wrapped = x;
}
public boolean isSatisfiedBy(Object candidate) {
return !wrapped.isSatisfiedBy(candidate);
}
}
图 10.14. SPECIFICATION的复合设计
Figure 10.14. COMPOSITE design of SPECIFICATION
编写此代码的目的是尽可能方便读者阅读。正如我所说,在某些情况下这样做效率可能不高。但是,其他实现选项也是可能的,这些选项可以最大限度地减少对象数量或提高速度,或者可能与某些项目中存在的特殊技术兼容。重要的是要有一个能够捕捉领域关键概念的模型,以及一个忠于该模型的实现。这为解决性能问题留下了很大的空间。
This code was written to be as easy as possible to read in a book. As I said, there may be situations in which this is inefficient. However, other implementation options are possible that would minimize object count or boost speed, or perhaps be compatible with idiosyncratic technologies present in some project. The important thing is a model that captures the key concepts of the domain, along with an implementation that is faithful to that model. That leaves a lot of room to solve performance problems.
此外,在很多情况下,这种完全通用性是不必要的。特别是,AND 的使用频率往往比其他的要高得多,而且它的实现复杂性也更低。如果您只需要 AND,那么不要害怕只实现它。
Also, this full generality is not needed in many cases. In particular, AND tends to be used a lot more than the others, and it also tends to create less implementation complexity. Don’t be afraid to implement only AND, if that is all you need.
回到第 2 章,在第 30页的示例对话框中,开发人员显然没有实现其SPECIFICATION的“满足”行为。在此之前,SPECIFICATION仅用于按订单构建。即便如此,抽象仍然完整,添加功能相对容易。使用模式并不意味着构建您不需要的功能。只要概念不混乱,它们可以稍后添加。
Way back in Chapter 2, in the example dialog on page 30, the developers had apparently not implemented the “satisfied by” behavior of their SPECIFICATION. Up to that point, the SPECIFICATION had been used only for building to order. Even so, the abstraction was intact, and adding functionality was relatively easy. Using a pattern doesn’t mean building features you don’t need. They can be added later, as long as the concepts don’t get muddled.
某些实现环境不能很好地适应非常细粒度的对象。我曾经在一个项目中使用对象数据库,该项目坚持为每个对象提供一个对象 ID,然后跟踪它。每个对象在内存空间和性能方面都有很多开销,而总地址空间是一个限制因素。我在域设计的一些重要点采用了SPECIFICATIONS,我认为这是一个很好的决定。但我使用了本章中描述的稍微更复杂的实现版本,这绝对是一个错误。它导致了数百万个非常细粒度的对象,导致系统陷入瘫痪。
Some implementation environments don’t accommodate very fine grained objects very well. I once worked on a project with an object database that insisted on giving an object ID to every object and then tracking it. Each object had lots of overhead in memory space and performance, and total address space was a limiting factor. I employed SPECIFICATIONS at some important points in the domain design, which I think was a good decision. But I used a slightly more elaborate version of the implementation described in this chapter, which was definitely a mistake. It resulted in millions of very fine grained objects that contributed to bogging the system down.
这是一个替代实现的示例,它将复合SPECIFICATION编码为编码逻辑表达式的字符串或数组,以便在运行时进行解释。
Here is an example of an alternative implementation that encodes the composite SPECIFICATION as a string or array encoding the logical expression, to be interpreted at runtime.
(如果您不知道如何实现这一点,请不要担心。重要的是要认识到,有很多方法可以使用逻辑运算符来实现 SPECIFICATION ,因此,如果简单的方法不适合您的情况下,您还可以选择。)
(Don’t worry if you do not see how you would implement this. The important thing is to realize that there are many ways of implementing a SPECIFICATION with logical operators, and so if the simple one is not practical in your situation, you have options.)
当你想测试一个候选对象时,你必须解释这个结构,这可以通过弹出每个元素,然后评估它或根据操作符的要求弹出下一个元素来完成。你最终会得到这样的结果:
When you want to test a candidate, you have to interpret this structure, which can be done by popping off each element, then evaluating it or popping off the next as required by an operator. You would end up with this:
且(非(装甲),非(通风))
and(not(armored), not(ventilated))
这种设计有优点(+)也有缺点(-):
This design has pros (+) and cons (–):
+对象数量低
+ Low object count
+高效利用内存
+ Efficient use of memory
–需要更成熟的开发人员
– Requires more sophisticated developers
您必须找到一种适合您情况的权衡利弊的实现方式。相同的模式和模型可以成为非常不同的实现方式的基础。
You have to find an implementation with trade-offs that work for your circumstances. The same pattern and model can underlie very different implementations.
这个最后的特性通常不需要,而且可能很难实现,但有时它可以解决一个非常困难的问题。它还阐明了 SPECIFICATION 的含义。
This final feature is not usually needed and can be difficult to implement, but every now and then it solves a really hard problem. It also elucidates the meaning of a SPECIFICATION.
再次考虑第235页示例中的化学品仓库包装工。回想一下,每种化学品都有一个容器规范,包装工 服务保证在将桶分配给容器时,所有这些都会得到满足。一切都很顺利……直到有人更改规定。
Consider again the chemical warehouse packer from the example on page 235. Recall that each Chemical had a Container Specification, and the Packer SERVICE guaranteed that all these would be satisfied when Drums are assigned to Containers. All is well... until someone changes the regulations.
每隔几个月就会发布一套新的规则,我们的用户希望能够列出现在要求更为严格的化学品类型的清单。
Every few months a new set of rules is issued, and our users would like to be able to produce a list of the chemical types that now have more stringent requirements.
当然,我们可以给出部分答案(用户可能也想要),通过对库存中的每个鼓进行验证,使用新的规格,并找出所有不再符合规格的鼓。这会告诉用户他们需要移动现有库存中的哪些鼓。
Of course, we could give a partial answer (and one the users probably also want) by running a validation of each Drum in the inventory, with the new SPECIFICATIONS in place, and finding all those that no longer meet the SPEC. This would tell the users which Drums in the existing inventory they need to move.
但他们要求的是一份处理方式更加严格的化学品清单。也许目前公司内部没有这些化学品,或者它们恰好被装在了更严格的容器中。无论是哪种情况,刚刚描述的报告都不会列出它们。
But what they asked for was a list of chemicals whose handling has become more stringent. Perhaps there are none in-house right now, or perhaps they just happened to be packed into a more stringent container. In either case, the report just described would not list them.
让我们介绍一种直接比较两个SPECIFICATIONS的新操作。
Let’s introduce a new operation for directly comparing two SPECIFICATIONS.
布尔值包含(其他规范);
boolean subsumes(Specification other);
更严格的SPEC会取代较不严格的 SPEC。它可以取代较不严格的 SPEC,而不会忽略任何先前的要求。
A more stringent SPEC subsumes a less stringent one. It could take its place without any previous requirement being neglected.
图 10.15.汽油容器的规格已收紧。
Figure 10.15. The SPECIFICATION for a gasoline container has been tightened.
用SPECIFICATION的语言来说,我们会说新的SPECIFICATION 包含了旧的SPECIFICATION,因为任何满足新 SPEC 的候选者也将满足旧 SPEC。
In the language of SPECIFICATION, we would say that the new SPECIFICATION subsumes the old SPECIFICATION, because any candidate that would satisfy the new SPEC would also satisfy the old.
如果将每个规范视为谓词,则包含等同于逻辑蕴涵。使用常规符号,A → B表示语句A蕴涵语句B,因此如果A为真,则 B也为真。
If each of these SPECIFICATIONS is viewed as a predicate, subsumption is equivalent to logical implication. Using conventional notation, A → B means that statement A implies statement B, so that if A is true, B is also true.
让我们将这个逻辑应用到我们的容器匹配需求中。当 SPECIFICATION 发生更改时,我们想知道提议的新 SPEC 是否满足旧 SPEC 的所有条件。
Let’s apply this logic to our container-matching needs. When a SPECIFICATION is being changed, we would like to know if the proposed new SPEC meets all the conditions of the old one.
新规格 → 旧规格
New Spec → Old Spec
也就是说,如果新规范为真,那么旧规范也为真。以一般方式证明逻辑蕴涵非常困难,但特殊情况则很容易。例如,特定参数化的SPECS可以定义自己的包容规则。
That is, if the new spec is true, then the old spec is also true. Proving a logical implication in a general way is very difficult, but special cases can be easy. For example, particular parameterized SPECS can define their own subsumption rule.
公共类MinimumAgeSpecification {
int 阈值;
公共布尔值isSatisfiedBy(Person 候选人){
返回候选人.getAge()> = 阈值;
}
公共布尔值subsumes(MinimumAgeSpecification 其他){
返回阈值> = 其他.getThreshold();
}
}
public class MinimumAgeSpecification {
int threshold;
public boolean isSatisfiedBy(Person candidate) {
return candidate.getAge() >= threshold;
}
public boolean subsumes(MinimumAgeSpecification other) {
return threshold >= other.getThreshold();
}
}
JUnit 测试可能包含以下内容:
A JUnit test might contain this:
drivingAge = 新的MinimumAgeSpecification(16);
votingAge = 新的MinimumAgeSpecification(18);
assertTrue(votingAge.subsumes(drivingAge));
drivingAge = new MinimumAgeSpecification(16);
votingAge = new MinimumAgeSpecification(18);
assertTrue(votingAge.subsumes(drivingAge));
另一个适合解决容器规范问题的实际特殊情况是将包含与单个逻辑运算符 AND 相结合的SPECIFICATION接口。
Another practical special case, one suited to address the Container Specification problem, is a SPECIFICATION interface combining subsumption with the single logical operator AND.
公共接口规范 {
boolean isSatisfiedBy(对象候选);
规范和(规范其他);
boolean subsumes(规范其他);
}
public interface Specification {
boolean isSatisfiedBy(Object candidate);
Specification and(Specification other);
boolean subsumes(Specification other);
}
仅使用 AND 运算符来证明蕴涵很简单:
Proving implication with only the AND operator is simple:
A与B → A
A AND B → A
或者,在更复杂的情况下:
or, in a more complicated case:
A与B与 C → A与B
A AND B AND C → A AND B
因此,如果复合规范能够收集所有通过“AND”连接在一起的叶子SPECIFICATION,那么我们所要做的就是检查包含的SPECIFICATION是否具有被包含的 SPECIFICATION 所具有的所有叶子,并且可能还具有一些额外的叶子 — — 它的叶子是另一个SPEC的叶子集的超集。
So if the Composite Specification is able to collect all the leaf SPECIFICATIONS that are “ANDed” together, then all we have to do is check that the subsuming SPECIFICATION has all the leaves that the subsumed one has, and maybe some extra ones as well—its leaves are a superset of the other SPEC’s set of leaves.
公共布尔值包含(规范其他){
如果(其他实例CompositeSpecification){
集合otherLeaves =
(CompositeSpecification)other.leafSpecifications();
迭代器it = otherLeaves.iterator();
而(it.hasNext()){
如果(!leafSpecifications()。contains(it.next())
返回false;
}
} else {
如果(!leafSpecifications()。contains(other))
返回false;
}
返回true;
}
public boolean subsumes(Specification other) {
if (other instanceof CompositeSpecification) {
Collection otherLeaves =
(CompositeSpecification) other.leafSpecifications();
Iterator it = otherLeaves.iterator();
while (it.hasNext()) {
if (!leafSpecifications().contains(it.next()))
return false;
}
} else {
if (!leafSpecifications().contains(other))
return false;
}
return true;
}
可以增强这种交互,以比较精心选择的参数化叶SPECIFICATIONS和一些其他复杂情况。不幸的是,当包含 OR 和 NOT 时,这些证明变得更加复杂。在大多数情况下,最好通过做出选择来避免这种复杂性,要么放弃一些运算符,要么放弃包含。如果两者都需要,请仔细考虑收益是否足以证明困难是合理的。
This interaction could be enhanced to compare carefully chosen parameterized leaf SPECIFICATIONS and some other complications. Unfortunately, when OR and NOT are included, these proofs become much more involved. In most situations it is best to avoid such complexity by making a choice, either forgoing some of the operators or forgoing subsumption. If both are needed, consider carefully if the benefit is great enough to justify the difficulty.
本章介绍了一系列技术,用于阐明代码的意图、使使用代码的后果透明化以及分离模型元素。即便如此,这种设计仍然很困难。你不能只看着一个庞大的系统说:“让我们让它变得灵活。”你必须选择目标。以下是几个广泛的方法,然后是一个扩展的例子,展示了如何将图案组合在一起并用于呈现更大的设计。
This chapter has presented a raft of techniques to clarify the intent of code, to make the consequences of using it transparent, and to decouple model elements. Even so, this kind of design is difficult. You can’t just look at an enormous system and say, “Let’s make this supple.” You have to choose targets. Here are a couple of broad approaches, followed by an extended example showing how the patterns are fit together and used to take on a bigger design.
您无法一次性解决整个设计问题。请逐步解决。系统的某些方面会向您提供方法,您可以将其分解并重新研究。您可能会看到模型的一部分可以被视为专门的数学;将其分开。您的应用程序强制执行限制状态更改的复杂规则;将其拉出到单独的模型或简单的框架中,以便您声明规则。通过每个这样的步骤,不仅新模块干净,而且留下的部分更小更清晰。剩下的部分以声明性样式编写,以特殊数学或验证框架的形式声明,或子域采用的任何形式。
You just can’t tackle the whole design at once. Pick away at it. Some aspects of the system will suggest approaches to you, and they can be factored out and worked over. You may see a part of the model that can be viewed as specialized math; separate that. Your application enforces complex rules restricting state changes; pull this out into a separate model or simple framework that lets you declare the rules. With each such step, not only is the new module clean, but also the part left behind is smaller and clearer. Part of what’s left is written in a declarative style, a declaration in terms of the special math or validation framework, or whatever form the subdomain takes.
对一个领域产生重大影响,使设计的一部分真正灵活,比分散精力更有用。第 15 章更深入地讨论了如何选择和管理子域。
It is more useful to make a big impact on one area, making a part of the design really supple, than to spread your efforts thin. Chapter 15 discusses in more depth how to choose and manage subdomains.
从头开始创建一个严密的概念框架并非每天都能做到。有时,您会在项目生命周期中发现并改进其中一个。但您通常可以使用和调整在您的领域或其他领域中长期建立的概念系统,其中一些系统经过了几个世纪的提炼和提炼。例如,许多商业应用涉及会计。会计定义了一套完善的实体和规则,可以轻松适应深度模型和灵活的设计。
Creating a tight conceptual framework from scratch is something you can’t do every day. Sometimes you discover and refine one of these over the course of the life of a project. But you can often use and adapt conceptual systems that are long established in your domain or others, some of which have been refined and distilled over centuries. Many business applications involve accounting, for example. Accounting defines a well-developed set of ENTITIES and rules that make for an easy adaptation to a deep model and a supple design.
有许多这样形式化的概念框架,但我个人最喜欢的是数学。令人惊讶的是,对基本算术进行一些改动是多么有用。许多领域都包含数学。寻找它。挖掘它。专业数学很清晰,可以通过明确的规则组合,人们发现它很容易理解。我过去的一个例子是“共享数学”,它将结束本章。
There are many such formalized conceptual frameworks, but my personal favorite is math. It is surprising how useful it can be to pull out some twist on basic arithmetic. Many domains include math somewhere. Look for it. Dig it out. Specialized math is clean, combinable by clear rules, and people find it easy to understand. One example from my past is “Shares Math,” which will end this chapter.
第 8 章讲述了一个银团贷款系统建设项目中模型突破的故事。现在我们将详细介绍这个例子,重点介绍与该项目类似的设计的一个特点。
Chapter 8 told the story of a model breakthrough on a project to build a syndicated loan system. Now this example will go into detail, focusing on just one feature of a design comparable to the one on that project.
该申请的一项要求是,当借款人支付本金时,这笔钱将默认按照贷款人在贷款中的份额按比例分配。
One requirement of that application was that when the borrower makes a principal payment, the money is, by default, prorated according to the lenders’ shares in the loan.
当我们重构它时,这个代码会变得更容易理解,所以不要停留在这个版本。
As we refactor it, this code will get easier to understand, so don’t get stuck on this version.
图 10.16
Figure 10.16
public class Loan {
private Map shares;
//不包括访问器,构造函数和非常简单的方法
public Map deliverPrincipalPayment(double paymentAmount) {
Map paymentShares = new HashMap();
Map loanShares = getShares();
double total = getAmount();
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
Object Owner = it.next();
double initialLoanShareAmount = getShareAmount(owner);
double paymentShareAmount =
initialLoanShareAmount / total * paymentAmount;
Share paymentShare =
new Share(owner, paymentShareAmount);
paymentShares.put(owner, paymentShare);
double newLoanShareAmount =
initialLoanShareAmount - paymentShareAmount;
共享 newLoanShare =
new Share(owner, newLoanShareAmount);
loanShares.put(owner, newLoanShare);
}
return paymentShares;
}
public double getAmount() {
Map loanShares = getShares();
double total = 0.0;
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
共享 loanShare = (Share) loanShares.get(it.next());
total = total + loanShare.getAmount();
}
return total;
}
}
public class Loan {
private Map shares;
//Accessors, constructors, and very simple methods are excluded
public Map distributePrincipalPayment(double paymentAmount) {
Map paymentShares = new HashMap();
Map loanShares = getShares();
double total = getAmount();
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
Object owner = it.next();
double initialLoanShareAmount = getShareAmount(owner);
double paymentShareAmount =
initialLoanShareAmount / total * paymentAmount;
Share paymentShare =
new Share(owner, paymentShareAmount);
paymentShares.put(owner, paymentShare);
double newLoanShareAmount =
initialLoanShareAmount - paymentShareAmount;
Share newLoanShare =
new Share(owner, newLoanShareAmount);
loanShares.put(owner, newLoanShare);
}
return paymentShares;
}
public double getAmount() {
Map loanShares = getShares();
double total = 0.0;
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
Share loanShare = (Share) loanShares.get(it.next());
total = total + loanShare.getAmount();
}
return total;
}
}
此设计已具有意图揭示接口。但该distributePaymentPrincipal()方法做了一件危险的事情:它计算分配的份额并修改贷款。让我们重构以将查询与修改器分开。
This design already has INTENTION-REVEALING INTERFACES. But the distributePaymentPrincipal() method does a dangerous thing: It calculates the shares for distribution and also modifies the Loan. Let’s refactor to separate the query from the modifier.
图 10.17
Figure 10.17
public void applyPrincipalPaymentShares(Map paymentShares) {
Map loanShares = getShares();
Iterator it = paymentShares.keySet().iterator();
while(it.hasNext()) {
Object lender = it.next();
共享 paymentShare = (Share) paymentShares.get(lender);
共享 loanShare = (Share) loanShares.get(lender);
double newLoanShareAmount = loanShare.getAmount() -
paymentShare.getAmount();
共享 newLoanShare = new Share(lender, newLoanShareAmount);
loanShares.put(lender, newLoanShare);
}
}
public Map calculatePrincipalPaymentShares(double paymentAmount) {
Map paymentShares = new HashMap();
Map loanShares = getShares();
double total = getAmount();
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
Object lender = it.next();
共享 loanShare = (Share) loanShares.get(lender);
double paymentShareAmount =
loanShare.getAmount() / total * paymentAmount;
共享 paymentShare = new Share(lender, paymentShareAmount);
paymentShares.put(lender, paymentShare);
}
return paymentShares;
}
public void applyPrincipalPaymentShares(Map paymentShares) {
Map loanShares = getShares();
Iterator it = paymentShares.keySet().iterator();
while(it.hasNext()) {
Object lender = it.next();
Share paymentShare = (Share) paymentShares.get(lender);
Share loanShare = (Share) loanShares.get(lender);
double newLoanShareAmount = loanShare.getAmount() -
paymentShare.getAmount();
Share newLoanShare = new Share(lender, newLoanShareAmount);
loanShares.put(lender, newLoanShare);
}
}
public Map calculatePrincipalPaymentShares(double paymentAmount) {
Map paymentShares = new HashMap();
Map loanShares = getShares();
double total = getAmount();
Iterator it = loanShares.keySet().iterator();
while(it.hasNext()) {
Object lender = it.next();
Share loanShare = (Share) loanShares.get(lender);
double paymentShareAmount =
loanShare.getAmount() / total * paymentAmount;
Share paymentShare = new Share(lender, paymentShareAmount);
paymentShares.put(lender, paymentShare);
}
return paymentShares;
}
客户端代码现在如下所示:
Client code now looks like this:
地图分布 =
aLoan.calculatePrincipalPaymentShares(paymentAmount);
aLoan.applyPrincipalPaymentShares(分布);
Map distribution =
aLoan.calculatePrincipalPaymentShares(paymentAmount);
aLoan.applyPrincipalPaymentShares(distribution);
还不错。函数在意图揭示接口背后封装了大量的复杂性。但是当我们添加applyDrawdown()、等等时,代码确实会开始增加一些calculateFeePaymentShares()。每次扩展都会使代码复杂化并使其变重。这可能是粒度太粗的一个点。传统方法是将计算方法分解为子程序。这可能是一个不错的步骤,但我们最终希望看到底层的概念边界并深化模型。具有这种概念轮廓粒度的设计元素可以组合起来以产生所需的变化。
Not too bad. The FUNCTIONS have encapsulated a lot of complexity behind INTENTION-REVEALING INTERFACES. But the code does begin to multiply some when we add applyDrawdown(), calculateFeePaymentShares(), and so on. Each extension complicates the code and weighs it down. This might be a point where the granularity is too coarse. The conventional approach would be to break the calculation methods down into subroutines. That could well be a good step along the way, but we ultimately want to see the underlying conceptual boundaries and deepen the model. The elements of a design with such a CONCEPT-CONTOURING grain could be combined to produce the needed variations.
现在有足够的指针来开始探索这个新模型。Share对象在这个实现中是被动的,它们以复杂的低级方式进行操纵。这是因为有关股票的大多数规则和计算并不适用于单个股票,而是适用于股票组。缺少一个概念:股票彼此相关,就像组成一个整体的各个部分一样。明确这个概念将使我们能够更简洁地表达这些规则和计算。
There are enough pointers now to start probing for that new model. The Share objects are passive in this implementation, and they are being manipulated in complex, low-level ways. This is because most of the rules and calculations about shares don’t apply to single shares, but to groups of them. There is a missing concept: shares are related to each other as parts making up a whole. Making this concept explicit will let us express those rules and calculations more succinctly.
图 10.18
Figure 10.18
Share Pie代表特定贷款的总分配。它是一个ENTITY ,其身份在贷款的AGGREGATE内是本地的。实际的分配计算可以委托给Share Pie。
The Share Pie represents the total distribution of a specific Loan. It is an ENTITY whose identity is local within the AGGREGATE of the Loan. The actual distribution calculations can be delegated to the Share Pie.
图 10.19
Figure 10.19
public class Loan {
private SharePie shares;
//省略访问器、构造函数和直接方法
public
Map calculatePrincipalPaymentDistribution(
double paymentAmount) {
return getShares().prorated(paymentAmount);
}
公共无效applyPrincipalPayment(Map paymentShares){
shares.decrease(paymentShares);
}
}
public class Loan {
private SharePie shares;
//Accessors, constructors, and straightforward methods
//are omitted
public Map calculatePrincipalPaymentDistribution(
double paymentAmount) {
return getShares().prorated(paymentAmount);
}
public void applyPrincipalPayment(Map paymentShares) {
shares.decrease(paymentShares);
}
}
贷款被简化了,份额计算集中在专注于该责任的VALUE OBJECT中。尽管如此,计算并没有真正变得更加通用或更易于使用。
The Loan is simplified, and the Share calculations are centralized in a VALUE OBJECT focused on that responsibility. Still, the calculations haven’t really become more versatile or easier to use.
通常,实施新设计的实际经验会引发对模型本身的新见解。在这种情况下,贷款和股份饼的紧密耦合似乎掩盖了股份饼和股份之间的关系。如果我们将股份饼设为VALUE OBJECT会发生什么?
Often, the hands-on experience of implementing a new design will trigger a new insight into the model itself. In this case, the tight coupling of the Loan and Share Pie seems to be obscuring the relationship of the Share Pie and the Shares. What would happen if we made Share Pie a VALUE OBJECT?
这意味着increase(Map)不允许decrease(Map),因为Share Pie必须是不可变的。要更改Share Pie的值,必须替换整个PieaddShares(Map) 。因此,您可以进行这样的操作,返回一个全新的、更大的Share Pie。
This would mean that increase(Map) and decrease(Map) would not be allowed, because the Share Pie would have to be immutable. To change the Share Pie’s value, the whole Pie would have to be replaced. So you could have operations such as addShares(Map) that would return a whole new, larger Share Pie.
让我们一路走到关闭运营。不是“增加”份额饼或向其中添加份额,而是将两个份额饼加在一起:结果是新的、更大的份额饼。
Let’s go all the way to CLOSURE OF OPERATIONS. Instead of “increasing” a Share Pie or adding Shares to it, just add two Share Pies together: the result is the new, larger Share Pie.
我们可以通过更改返回类型来部分关闭Share Pieprorate()上的操作。将其重命名为强调其没有副作用。“Shares Math”开始成形,最初有四个操作。prorated()
We can partially close the prorate() operation over Share Pie just by changing the return type. Renaming it to prorated() emphasizes its lack of side effects. “Shares Math” starts to take shape, initially with four operations.
图 10.20
Figure 10.20
我们可以对我们的新值对象(Share Pies)做出一些明确的断言。每种方法都有其意义。
We can make some well-defined ASSERTIONS about our new VALUE OBJECTS, the Share Pies. Each method means something.
public class SharePie {
private Map shares = new HashMap();
//省略了访问器和其他直接方法
public double getAmount() {
double total = 0.0;
Iterator it = shares.keySet().iterator();
while(it.hasNext()) { Share loanShare = getShare(it.next()); 整体等于其各部分之和。 total = total + loanShare.getAmount(); } return total; } public SharePie minus(SharePie otherShares) { SharePie result = new SharePie(); Set holders = new HashSet(); holders.addAll(getOwners()); holders.addAll(otherShares.getOwners()); Iterator it = holders.iterator(); 两个Pie之间的区别是 while(it.hasNext()) { 每个所有者的 Object Owner = it.next(); 份额之间的区别。 double resultShareAmount = getShareAmount(owner) – otherShares.getShareAmount(owner); result.add(owner, resultShareAmount); } return result; } public SharePie plus(SharePie otherShares) { 两个饼图的组合 //与 minus() 的实现类似 } 是各个所有者份额的组合。 public SharePie prorated(double amountToProrate) { SharePie proration = new SharePie(); double basis = getAmount(); 一定数量可以在所有股东之间按比例分配 Iterator it = shares.keySet().iterator(); while(it.hasNext()) { Object Owner = it.next(); Share share = getShare(owner);
double proratedShareAmount =
share.getAmount() / basis * amountToProrate;
proration.add(owner, proratedShareAmount);
}
返回 proration;
}
}
public class SharePie {
private Map shares = new HashMap();
//Accessors and other straightforward methods are omitted
public double getAmount() {
double total = 0.0;
Iterator it = shares.keySet().iterator();
while(it.hasNext()) { The whole is equal to the
Share loanShare = getShare(it.next()); sum of its parts.
total = total + loanShare.getAmount();
}
return total;
}
public SharePie minus(SharePie otherShares) {
SharePie result = new SharePie();
Set owners = new HashSet();
owners.addAll(getOwners());
owners.addAll(otherShares.getOwners()); The difference between
Iterator it = owners.iterator(); two Pies is the difference
while(it.hasNext()) { between each owner's
Object owner = it.next(); share.
double resultShareAmount = getShareAmount(owner) –
otherShares.getShareAmount(owner);
result.add(owner, resultShareAmount);
}
return result;
}
public SharePie plus(SharePie otherShares) { The combination of two
//Similar to implementation of minus() Pies is the combination of
} each owner's share.
public SharePie prorated(double amountToProrate) {
SharePie proration = new SharePie();
double basis = getAmount(); An amount can be divided
Iterator it = shares.keySet().iterator(); proportionately
while(it.hasNext()) { among all shareholders.
Object owner = it.next();
Share share = getShare(owner);
double proratedShareAmount =
share.getAmount() / basis * amountToProrate;
proration.add(owner, proratedShareAmount);
}
return proration;
}
}
此时,最重要的Loan类中的方法可以简单如下:
At this point, the methods in the all-important Loan class could be as simple as this:
public class Loan {
private SharePie shares; // 省略了
访问器、构造函数和直接方法 public SharePie calculatePrincipalPaymentDistribution( double paymentAmount) { return shares.prorated(paymentAmount); } public void applyPrincipalPayment(SharePie paymentShares) { setShares(shares.minus(paymentShares)); }
public class Loan {
private SharePie shares;
//Accessors, constructors, and straightforward methods
//are omitted
public SharePie calculatePrincipalPaymentDistribution(
double paymentAmount) {
return shares.prorated(paymentAmount);
}
public void applyPrincipalPayment(SharePie paymentShares) {
setShares(shares.minus(paymentShares));
}
这些简短方法中的每一个都说明了其含义。应用本金支付意味着您从贷款中按份额扣除支付。分配本金支付是通过按比例在股东之间分配金额来完成的。Share Pie 的设计使我们能够在贷款代码中使用声明式风格,生成的代码读起来就像是业务交易的概念定义,而不是计算。
Each of these short methods states its meaning. Applying a principal payment means that you subtract the payment from the loan, share by share. Distributing a principal payment is done by dividing the amount pro rata among the shareholders. The design of the Share Pie has allowed us to use a declarative style in the Loan code, producing code that begins to read like a conceptual definition of the business transaction, rather than a calculation.
现在可以轻松声明其他交易类型(以前太复杂,无法列出)。例如,贷款提取额根据贷方在 Facility 中的份额在贷方之间分配。新的提取额将添加到未偿还的Loan中。在我们的新领域语言中:
Other transaction types (too complicated to list before) can be declared easily now. For example, loan drawdowns are divided among lenders based on their shares of the Facility. The new draw-down is added to the outstanding Loan. In our new domain language:
公共类设施{
私人SharePie股份;
...
公共 SharePie calculateDrawdownDefaultDistribution(
double drawdownAmount) {
return shares.prorated(drawdownAmount);
}
}
公共类贷款 {
. . .
公共 void applyDrawdown(SharePie drawdownShares) {
setShares(shares.plus(drawdownShares));
}
}
public class Facility {
private SharePie shares;
. . .
public SharePie calculateDrawdownDefaultDistribution(
double drawdownAmount) {
return shares.prorated(drawdownAmount);
}
}
public class Loan {
. . .
public void applyDrawdown(SharePie drawdownShares) {
setShares(shares.plus(drawdownShares));
}
}
要了解每个贷款人与其商定贡献的偏差,请取未偿还贷款金额的理论分配,并将其从贷款的实际份额中减去:
To see the deviation of each lender from its agreed contribution, take the theoretical distribution of the outstanding Loan amount and subtract it from the Loan’s actual shares:
SharePie 原始协议 =
aFacility.getShares().prorated(aLoan.getAmount());
SharePie 实际 = aLoan.getShares();
SharePie 偏差 = 实际.减去(原始协议);
SharePie originalAgreement =
aFacility.getShares().prorated(aLoan.getAmount());
SharePie actual = aLoan.getShares();
SharePie deviation = actual.minus(originalAgreement);
Share Pie设计的某些特性使得代码中的重组和通信变得容易。
Certain characteristics of the Share Pie design make for this easy recombination and communication in the code.
•复杂逻辑被封装在具有无副作用函数的专门值对象中。大多数复杂逻辑都封装在这些不可变对象中。由于Share Pies是值对象,因此数学运算可以创建新实例,我们可以自由使用这些实例来替换过时的实例。
• Complex logic is encapsulated in specialized VALUE OBJECTS with SIDE-EFFECT-FREE FUNCTIONS. Most complex logic has been encapsulated in these immutable objects. Because Share Pies are VALUE OBJECTS, the math operations can create new instances, which we can use freely to replace outdated instances.
Share Pie 的任何方法都不会对现有对象造成任何改变。这使我们能够在中间计算中自由使用plus()、minus()和,prorated()将它们组合起来,期望它们按照名称所暗示的那样工作,仅此而已。它还使我们能够基于相同的方法构建分析特征。(以前,只有在进行实际分配时才能调用它们,因为每次调用后数据都会发生变化。)
None of the Share Pie methods causes any change to any existing object. This allows us to use plus(), minus(), and prorated() freely in intermediate calculations, combining them, expecting them to do what their names suggest, and nothing more. It also allows us to build analytical features based on the same methods. (Before, they could be called only when an actual distribution was made, because the data would change after each call.)
•状态修改操作简单,具有断言的特点。Shares Math 的高级抽象允许以声明式的方式简洁地编写交易的不变量样式。例如,偏差是实际饼图减去根据设施份额饼图按比例分配的贷款金额。
• State-modifying operations are simple and characterized with ASSERTIONS. The high-level abstractions of Shares Math allow invariants of transactions to be written concisely in a declarative style. For example, the deviation is the actual pie minus the Loan amount prorated based on the Facility’s Share Pie.
•模型概念是解耦的;操作会纠缠最少的其他类型。Share Pie上的某些方法表现出操作的闭包(在Share Pie下,添加或减去的方法已关闭)。其他方法将简单的数量作为参数或返回值;它们不是封闭的,但它们对概念负载的影响很小。Share Pie仅与另一个类Share紧密交互。因此,Share Pie是自包含的、易于理解、易于测试的,并且易于组合以形成声明性事务。这些属性是从数学形式主义中继承下来的。
• Model concepts are decoupled; operations entangle a minimum of other types. Some methods on Share Pie exhibit CLOSURE OF OPERATIONS (the methods to add or subtract are closed under Share Pies). Others take simple amounts as arguments or return values; they are not closed, but they add little to the conceptual load. The Share Pie interacts closely with only one other class, Share. As a result, the Share Pie is self-contained, easily understood, easily tested, and easily combined to form declarative transactions. These properties were inherited from the math formalism.
•熟悉的形式使协议易于掌握。可以基于金融术语设计出一种完全原创的股票操纵协议。原则上,它可以变得灵活。但它有两个缺点。首先,它必须被发明,这是一项困难且不确定的任务。其次,每个处理它的人都必须学习它。看到股票数学的人会认出他们已经知道的系统,而且由于设计一直与算术规则保持一致,这些人不会被误导。
• Familiar formalism makes the protocol easy to grasp. A wholly original protocol for manipulating shares could have been devised based on financial terminology. In principle, it could have been made supple. But it would have had two disadvantages. First, it would have to be invented, a difficult and uncertain task. Second, it would have to be learned by each person who dealt with it. People who see Shares Math recognize a system they already know, and because the design has been kept carefully consistent with the rules of arithmetic, those people are not misled.
提取与数学形式相对应的问题部分,我们得出了一个灵活的Shares设计,进一步提炼了核心的Loan和Facility方法。(有关核心域的讨论,请参阅第 15 章。)
Pulling out the part of the problem that corresponded to the formalism of math, we arrived at a supple design for Shares that further distills the core Loan and Facility methods. (See Chapter 15 for discussion of the CORE DOMAIN.)
柔性设计对软件应对变化和复杂性的能力有着深远的影响。正如本章中的示例所示,它通常取决于非常详细的建模和设计决策。其影响可能超出特定的建模和设计问题。第 15 章将讨论柔性设计的战略价值,它是提炼领域模型以使大型复杂项目更易于处理的几种工具之一。
Supple design has a profound effect on the ability of software to cope with change and complexity. As the examples in this chapter have shown, it often hinges on quite detailed modeling and design decisions. The impact can go beyond a specific modeling and design problem. Chapter 15 will discuss the strategic value of supple design as one of several tools for distilling a domain model to make large and complex projects more tractable.
深度模型和灵活的设计来之不易。进步来自于对领域的大量学习、大量的交流和大量的尝试和错误。不过,有时我们可以取得优势。
Deep models and supple designs don’t come easily. Progress comes from lots of learning about the domain, lots of talking, and lots of trial and error. Sometimes, though, we can get a leg up.
当经验丰富的开发人员在研究领域问题时看到熟悉的责任或熟悉的关系网时,他或她可以借鉴以前解决问题的记忆。尝试了哪些模型,哪些模型有效?实施过程中出现了哪些困难,如何解决?以前经验中的反复试验突然与新情况相关。其中一些模式已被记录和共享,让我们其他人可以借鉴积累的经验。
When an experienced developer looking at a domain problem sees a familiar sort of responsibility or a familiar web of relationships, he or she can draw on the memory of how the problem was solved before. What models were tried and which worked? What difficulties arose in implementation and how were they resolved? The trial and error of that earlier experience is suddenly relevant to the new situation. Some of these patterns have been documented and shared, allowing the rest of us to draw on the accumulated experience.
与第二部分中介绍的基本构建块模式和第 10 章中灵活的设计原则相比,这些模式更高级、更专业,涉及使用一些对象来表示某些概念。它们让我们能够通过昂贵的反复试验,从一个已经富有表现力和可实现的模型开始,并解决可能花费高昂学习成本的细微之处。从这个起点开始,我们进行重构和实验。这些不是现成的解决方案。
In contrast to the fundamental building block patterns presented in Part II, and the supple design principles of Chapter 10, these patterns are higher level and more specialized, involving the use of a few objects to represent some concept. They let us cut through expensive trial and error to start with a model that is already expressive and implementable and addresses subtleties that might be costly to learn. From that starting point, we refactor and experiment. These are not out-of-the-box solutions.
在分析模式:可重用对象模型中,Martin Fowler 这样定义他的模式:
In Analysis Patterns: Reusable Object Models, Martin Fowler defined his patterns this way:
分析模式是代表业务建模中常见构造的概念组。它可能仅与一个领域相关,也可能跨越多个领域。[ Fowler 1997,第 8 页]
Analysis patterns are groups of concepts that represent a common construction in business modeling. It may be relevant to only one domain or it may span many domains. [Fowler 1997, p. 8]
Fowler 提出的分析模式源自该领域的经验,因此在适当的情况下,它们是实用的。此类模式为面临挑战的领域的人提供了非常有价值的起点,帮助他们进行迭代开发过程。名称强调了它们的概念性。分析模式不是技术解决方案;它们是帮助您在特定领域制定模型的指南。
The analysis patterns Fowler presents arose from experience in the field, and so they are practical, in the right situation. Such patterns provide someone facing a challenging domain with very valuable starting points for their iterative development process. The name emphasizes their conceptual nature. Analysis patterns are not technological solutions; they are guides to help you work out a model in a particular domain.
不幸的是,名称没有传达出这些模式中对实现的大量讨论,包括一些代码。福勒了解不考虑实际设计的分析的缺陷。这是一个有趣的例子,他甚至超越了部署,关注特定模型选择对现场系统长期维护的影响:
What the name unfortunately does not convey is that there is significant discussion of implementation in these patterns, including some code. Fowler understands the pitfalls of analysis without thought for practical design. Here is an interesting example where he is looking even beyond deployment, to the implications of specific model choices on the long-term maintenance of the system in the field:
当我们建立新的[会计]实践时,我们会创建一个新的过账规则实例网络。我们可以在系统仍在运行的情况下做到这一点,而无需重新编译或重建系统。在不可避免的情况下,我们需要一种新的过账规则子类型,但这种情况很少见。[第151页]
When we build a new [accounting] practice, we create a network of new instances of the posting rule. We can do this without any recompilation or rebuilding of the system, while it is still up and running. There will be unavoidable occasions when we need a new subtype of posting rule, but these will be rare. [p. 151]
在成熟的项目中,模型选择通常取决于应用经验。人们会尝试各种组件的多种实现。其中一些组件将投入生产,甚至将面临维护阶段。有了这样的经验,许多问题就可以避免。分析模式在最好的情况下可以借鉴其他项目的经验,将模型见解与设计方向和实施后果的广泛讨论相结合。脱离这种背景讨论模型思想会使它们更难应用,并有可能打开分析和设计之间的致命鸿沟,这与模型驱动设计背道而驰。
On a mature project, model choices are often informed by experience with the application. Multiple implementations of various components will have been tried. Some of these will have been carried into production and even will have faced the maintenance phase. Many problems can be avoided when such experience is available. Analysis patterns at their best can carry that kind of experience from other projects, combining model insights with extensive discussions of design directions and implementation consequences. To discuss model ideas out of that context makes them harder to apply and risks opening the deadly divide between analysis and design, which is antithetical to MODEL-DRIVEN DESIGN.
通过示例比通过抽象描述可以更好地解释分析模式的原理和应用。在本章中,我将给出两个开发人员使用Fowler 1997 年“库存和会计”一章中一小部分具有代表性的模型示例。分析模式将进行总结,仅用于支持示例。这显然不是试图对此类模式进行分类,甚至不是试图完全解释示例模式。重点是说明它们如何集成到领域驱动设计过程中。
The principle and application of analysis patterns can be explained better by example than through abstract description. In this chapter, I will give two examples of developers making use of a small, representative sample of models from the chapter “Inventory and Accounting” in Fowler 1997. The analysis patterns will be summarized just enough to support the examples. This is obviously not an attempt to catalog patterns of this kind or even to fully explain the sample patterns. The point is to illustrate their integration into the domain-driven design process.
第 10 章展示了开发人员为特定专业会计应用程序寻找更深层次模型的各种可能方式。这是另一种情况。这一次,开发人员将挖掘 Fowler 的《分析模式》一书来寻找有用的想法。
Chapter 10 showed various possible ways that a developer might search for a deeper model for a particular specialty accounting application. Here is yet another scenario. This time, the developers will mine Fowler’s Analysis Patterns book for useful ideas.
回顾一下,一款用于跟踪贷款和其他生息资产的应用程序会计算产生的利息和费用,并跟踪借款人的付款情况。每晚的批处理过程会获取这些数据并将其传递给旧式会计系统,指示每笔金额应记入的具体分类账。该设计有效,但使用起来很麻烦,更改起来很棘手,而且沟通不畅。
To review, an application for tracking loans and other interest-bearing assets calculates the interest and fees generated and tracks payments from the borrower. A nightly batch process takes those figures and passes them to the legacy accounting system, indicating the specific ledger each amount should be posted to. The design works, but it is awkward to use, tricky to change, and does not communicate well.
图 11.1. 初始类图
Figure 11.1. The initial class diagram
开发人员决定阅读《分析模式》的第 6 章“库存和会计”。以下是她认为最相关部分的摘要。
The developer decides to read Chapter 6 in Analysis Patterns, “Inventory and Accounting.” Here is a summary of the part she found most relevant.
分析模式中的会计模型
Accounting Models in Analysis Patterns
各种商业应用程序都会跟踪账户,账户中存放着有价值的东西,通常是钱。在许多应用程序中,仅仅跟踪账户中的金额是不够的。记录和控制该金额的每次变化至关重要。这是最基本的会计模型的动机。
Business applications of all sorts track accounts, which hold things of value, typically money. In a lot of applications, it isn’t enough to keep track of the amount in an account. It is essential to account for and control each change to that amount. That is the motivation for the most basic of the accounting models.
图 11.2. 基本会计模型
Figure 11.2. A basic accounting model
可以通过插入条目来添加值。可以通过插入负条目来删除值。条目永远不会被删除,因此整个历史记录都会保留。余额是所有条目的综合效果。此余额可以根据需求计算或缓存,这是由Account接口封装的实现决策。
Value can be added by inserting an Entry. Value can be removed by inserting a negative Entry. Entries are never removed, so the whole history is retained. The balance is the combined effect of all Entries. This balance could be computed on demand or cached, an implementation decision that is encapsulated by the Account interface.
会计的一个基本原则是守恒定律。钱不会凭空出现,也不会消失得无影无踪。它只是从一个账户转移到另一个账户。
A basic principle of accounting is conservation. Money doesn’t appear out of nowhere, nor does it disappear without a trace. It is only moved from one Account to another.
图 11.3. 交易模型
Figure 11.3. A transaction model
这是复式记账的既定概念:每笔贷方都有相应的借方。当然,与其他保护原则一样,它只适用于封闭系统,即包含所有来源和汇的系统。许多简单的应用不需要这种严格性。
This is the well-established concept of double-entry book-keeping: Every credit has a matching debit. Of course, like other conservation principles, it applies only to a closed system, one that includes all sources and sinks. Many simple applications do not require this rigor.
在他的书中,福勒包含了这些模型的更详细形式,并对权衡利弊进行了大量讨论。
In his book, Fowler includes more elaborate forms of these models and considerable discussion of the trade-offs.
这次阅读让开发人员(开发人员 1)产生了一些新想法。她将这一章展示给了一位同事(开发人员 2),这位同事一直与她一起研究一些利息计算逻辑,并且编写了夜间批处理程序。他们一起粗略地修改了他们的模型,并结合了他们读到的一些模型元素。
This reading gives the developer (Developer 1) several new ideas. She shows the chapter to a colleague (Developer 2) who has been working on some of the interest calculation logic with her and who wrote the nightly batch program. Together, they rough out a change to their model, incorporating some of the model elements they’ve read about.
图 11.4. 新模型建议
Figure 11.4. The new model proposal
Then they pull in their domain expert (Expert) for a discussion of their new model ideas.
开发人员 1:使用这个新模型,我们会将利息收入记入利息账户,而不仅仅是调整 interestDueAmount。然后,再记入另一笔付款以平衡利息收入。
Developer 1: With this new model, we make an Entry into the Interest Account for the interest earned, rather than just adjusting the interestDueAmount. Then, another Entry for the payment balances it out.
专家:那么现在我们不仅能看到所有利息的累计记录,还能看到还款记录了?这正是我们一直想要的。
Expert: So now we’d be able to see a history of all the interest accruals as well as the payment history? That’s something we’ve been wanting.
开发人员 2:我不确定我们是否正确使用了“交易”。该定义指的是将资金从一个帐户转移到另一个帐户,而不是同一帐户中相互平衡的两个条目。
Developer 2: I’m not sure we’ve used “Transaction” quite right. The definition talks about moving money from one Account to another, not two entries that balance each other in the same Account.
开发人员 1:这是一个很好的观点。我还担心这本书似乎强调了一次性创建交易的重要性。利息支付可能会延迟几天。
Developer 1: That’s a good point. I was also worried that the book seems to make quite a point about the transaction being created all at once. The interest payments can be several days late.
专家:这些付款不一定是延迟付款。付款时间有很大的灵活性。
Expert: Those payments aren’t necessarily late. There is a lot of flexibility in when they pay.
开发人员 1:所以这可能是一条死胡同。我在想我们可能已经确定了一些隐含的概念。让利息计算器创建Entry对象似乎确实可以更好地进行沟通。而Transaction似乎巧妙地将计算出的利息与付款联系在一起。
Developer 1: So this may be a blind alley. I was thinking we might have identified some implicit concepts. Having the Interest Calculator create Entry objects does seem to communicate better. And Transaction seemed to neatly tie together the calculated interest with the payment.
专家:为什么我们需要将应计款项与付款款项联系起来?它们是会计系统中的单独过账。账户余额才是最重要的。结合各个条目,我们确实拥有了所需的一切。
Expert: Why do we need to tie together the accrual to the payment? They are separate postings in the accounting system. The balance on the Account is the main thing. Along with the individual Entries, we really have what we need.
开发人员 2:您的意思是您不追踪他们是否支付了利息?
Developer 2: You mean you don’t track whether they’ve made the interest payment?
专家:当然有。但并不像你们的这种一次累计/一次支付方案那么简单。
Expert: Well, of course we do. But it isn’t as simple as this one-accrual/one-payment scheme of yours.
开发人员 2:它实际上可以简化很多事情,让人不再担心这种连接。
Developer 2: It could actually simplify a lot of things to stop worrying about that connection.
开发人员 1:好的,这样如何?[复制旧类图并开始勾画修改] 顺便说一句,您使用了几次“应计”这个词。您能解释一下它的意思吗?
Developer 1: OK, how about this? [Takes copy of old class diagram and starts sketching modifications] By the way, you used the word accruals a few times. Could you clarify what it means?
专家:当然。应计就是在费用或收入发生时记入账户,而不考虑钱实际转手的时间。因此,我们每天都会累积利息,但在月底(例如)我们会收到一笔付款。
Expert: Sure. An accrual is just when you account for an expense or income at the time it is incurred, never mind when money actually changes hands. So, we accrue interest every day, but at the end of the month (for example) we receive a payment against it.
开发人员 1:是的,我们确实需要这样的词。好的,这个看起来怎么样?
Developer 1: Yes, we really needed a word like that. OK, how does this look?
图 11.5. 原始类图,应计项目与付款分开
Figure 11.5. Original class diagram, accruals separated from payment
开发人员 1:现在我们可以摆脱计算器中与付款相关的所有复杂性,并且我们引入了应计一词,它可以更好地揭示意图。
Developer 1: Now we can get rid of all the complications that were in the calculator from relating payments, and we’ve introduced the term accruals, which reveals the intent better.
专家:那么,我们不会使用帐户对象吗?我期待能够在那里看到所有内容,包括应计项目、付款和余额。
Expert: So we’re not going to have the Account object? I was looking forward to being able to see everything together there, with the accruals and the payments and a balance.
开发人员 1:真的吗?好吧,这样也许可行。[获取其他图表和草图]
Developer 1: Really?! Well in that case, maybe this would work. [Takes other diagram and sketches]
图 11.6. 基于账户的图表,不含交易
Figure 11.6. The account-based diagram, without Transaction
专家:确实看上去很不错!
Expert: That actually looks pretty good!
开发人员 2:批处理脚本将很容易更改以使用这些新对象。
Developer 2: The batch script will be easy to change to use these new objects.
开发人员 1:新的利息计算器需要几天时间才能运行。有相当多的测试需要更改。但之后测试会读起来更清晰。
Developer 1: It will take a few days to get the new Interest Calculator working. There are quite a few tests to change. But the test will read clearer afterward.
两位开发人员开始根据新模型进行重构。当他们开始着手处理代码并完善设计时,他们获得了改进模型的见解。
The two developers went off and started refactoring based on the new model. As they got their hands on the code, tightening up the design, they had insights that refined the model.
条目被细分为付款和应计,因为仔细检查会发现应用程序中对这些条目的职责略有不同,并且它们都是重要的领域概念。另一方面,条目之间没有概念或行为上的区别,无论它们是费用还是利息。它们只是出现在相应的帐户中。
Entries were subclassed into Payment and Accrual because closer inspection revealed slightly different responsibilities in the application for these, and because they were both important domain concepts. On the other hand, there was no conceptual or behavioral distinction between Entries based on whether they resulted from fees or interest. They simply appeared in the appropriate Account.
然而,不幸的是,开发人员发现他们不得不放弃这最后的抽象来实现。数据存储在关系表中,项目标准是使这些表无需运行程序即可解释。这意味着将费用条目和利息条目保存在单独的表中。开发人员做到这一点的唯一方法是使用他们特定的对象关系映射框架,就是要创建具体的子类(费用支付、利息支付等)。如果使用不同的基础设施,他们可能可以避免这种笨拙的扩展。
Yet, unfortunately, the developers found they had to give up this last abstraction for the implementation. Data was stored in relational tables, and the project standard was to make those tables interpretable without running the program. This meant keeping fee entries and interest entries in separate tables. The only way for developers to do this, using their particular object-relational mapping framework, was to make concrete subclasses (Fee Payments, Interest Payments, and so on). With different infrastructure, they might have avoided this clumsy expansion.
我把这个转折放入这个虚构的故事中,以代表我们经常遇到的现实难题。我们必须做出深思熟虑的妥协,然后继续前进,不要让它让我们偏离我们的模型驱动设计。
I threw this twist into this largely fictitious story to represent the rub of reality that we encounter all the time. We have to make calculated compromises and then move on without letting it throw us off our MODEL-DRIVEN DESIGN.
图 11.7. 实现后的类图
Figure 11.7. The class diagram after the implementation
新设计更容易分析和测试,因为最复杂的功能是在无副作用函数中。其余命令的代码很简单(因为它调用了各种函数),并以断言为特征。
The new design was much easier to analyze and test because the most complex functionality is in SIDE-EFFECT-FREE FUNCTIONS. The remaining command has simple code (because it calls various FUNCTIONS) and is characterized by ASSERTIONS.
有时,我们甚至不认为程序的某些部分能够从领域模型中获益。它们可能一开始非常简单,然后机械地发展。它们看起来像复杂的应用程序代码,而不是领域逻辑。分析模式在向我们展示这些盲点方面特别有用。
Sometimes there are parts of our programs that we don’t even suspect have the potential to benefit from a domain model. They may have started very simply and evolved mechanistically. They seem like complicated application code, rather than domain logic. Analysis patterns can be particularly helpful in showing us these blind spots.
在下面的例子中,开发人员对夜间批处理的黑匣子有了新的认识,而这之前并没有被认为是面向领域的。
In the following example, a developer has a new insight into the black box of the nightly batch, which had not been considered domain oriented.
几周后,改进的基于帐户的模型开始稳定下来。正如经常发生的那样,新设计的清晰度使其他问题更加明显。调整夜间批处理以与新设计交互的开发人员(开发人员 2 )开始看到批处理行为与分析模式中的一些概念之间的联系。以下是他认为最相关的一些概念的摘要。
After a few weeks, the improved Account-based model had started to settle in. As often happens, the clarity of the new design made other problems more visible. The developer (Developer 2) who was adapting the nightly batch to interact with the new design began to see connections between the behavior of the batch and some of the concepts in Analysis Patterns. Here is a summary of some of the concepts he found most relevant.
发帖规则
Posting Rules
会计系统通常会提供相同基本财务信息的多种视图。一个账户可能跟踪收入,而另一个账户可能跟踪该收入的预估税。如果系统需要自动更新预估税账户,那么这两个账户的实施就会变得非常复杂。有些系统中的大多数账户条目都来自此类规则;在这样的系统中,依赖逻辑会变得一团糟。即使在更简单的系统中,这种交叉发布也可能很棘手。解决依赖关系混乱的第一步是通过引入新对象使这些规则明确化。
Accounting systems often provide multiple views of the same basic financial information. One account might track income while another might track an estimated tax on that income. If the system is expected to automatically update the estimated tax account, the implementation of those two accounts becomes very intertwined. There are systems in which the majority of account entries result from such rules; in such a system, the dependency logic gets to be a mess. Even in more modest systems, such cross-posting can be tricky. The first step toward taming the tangle of dependencies is to make these rules explicit by introducing a new object.
图 11.8. 基本发布规则的类图
Figure 11.8. The class diagram of the basic posting rule
过账规则由其“输入”帐户中的新条目触发。然后,它会派生出一个新条目(基于其自己的计算方法),并将新条目插入其“输出”帐户。在工资系统中,工资帐户中的条目可能会触发过账规则,该规则将计算 30% 的预估所得税并将其作为条目插入税收预扣帐户中。
A posting rule is triggered by a new Entry in its “input” account. It then derives a new Entry (based on its own calculation Method) and inserts the new Entry into its “output” Account. In a payroll system, an Entry in a salary Account might trigger a Posting Rule that would calculate a 30 percent estimated income tax and insert it as an Entry in the tax with-holding Account.
过账规则已建立帐户之间的概念依赖关系,但如果模式止步于此,则可能难以遵循。依赖关系设计中最棘手的部分之一是更新的时间和控制。Fowler 讨论了三种选择。
The Posting Rule has established the conceptual dependency between Accounts, but if the pattern stopped there, it could be difficult to follow. One of the trickiest parts of dependency designs is the timing and control of updates. Fowler discusses three options.
1. “急切触发”是最明显的,但通常最不实用。每当将条目插入帐户时,它都会立即触发发布规则,并且所有更新都会立即进行。
1. “Eager firing” is the most obvious, but typically the least practical. Whenever an Entry is inserted into an Account, it immediately triggers the Posting Rules and all updates are made immediately.
2. “基于帐户的触发”允许延迟处理。在某个时刻,一条消息被发送到一个帐户,并触发其发布规则来处理自上次触发以来插入的所有条目。
2. “Account-based firing” allows processing to be deferred. At some point, a message is sent to an Account and it triggers its Posting Rules to process all Entries inserted since its last firing.
3.最后,“基于过账规则的触发”由外部代理发起,告知过账规则触发。过账规则负责查找自上次触发以来对其输入帐户进行的所有分录。
3. Finally, “Posting-Rule-based firing” is initiated by an external agent, which tells the Posting Rule to fire. The Posting Rule is responsible for looking up all Entries made to its input Accounts since the last time it fired.
尽管触发模式可以在系统中混合使用,但每组特定的规则都需要有一个明确定义的启动点和识别输入帐户条目的责任。将三种触发模式添加到UBIQUITOUS LANGUAGE对模式的成功与模型对象定义本身一样重要。它消除了歧义并将决策直接引导到一组明确定义的选择上。这些模式识别出容易被忽视的挑战并提供支持清晰讨论的词汇。
Although firing modes can be mixed in a system, each particular set of rules needs to have one clearly defined point of initiation and responsibility for identifying input Account Entries. The addition of the three firing modes to the UBIQUITOUS LANGUAGE is as important to the success of the pattern as the model object definitions themselves. It eliminates ambiguity and guides decision making directly to a clearly defined set of choices. These modes identify an easily overlooked challenge and provide vocabulary to support clear discussion.
开发人员 2需要一个可以讨论其新想法的人。他与同事(开发人员 1)见了面,后者是主要负责应计项目建模的开发人员。
Developer 2 needed a sounding board to discuss his new ideas. He met up his colleague (Developer 1), the developer who had been primarily responsible for modeling the accruals.
开发人员 2:在某个时候,夜间批处理开始成为我们掩盖事情的地方。脚本所做的事情中隐含着领域逻辑,而且变得越来越复杂。很长一段时间以来,我一直想做一个批处理采用模型驱动设计,分离出一个域层,并使脚本本身成为域之上的简单层。但我永远无法弄清楚该域模型会是什么样子。似乎可能只是一些作为对象没有真正意义的过程。当我阅读分析模式中关于发布规则的部分时,我得到了一些想法。这就是我的想法。[递给我一张草图]
Developer 2: At some point, the nightly batch started being a place where we swept stuff under the rug. There is domain logic implicit in what the script does, and it’s been getting more and more complicated. For a long time I’ve wanted to do a model-driven design for the batch, separate out a domain layer, and make the script itself a simple layer on top of the domain. But I could never figure out what that domain model would be like. It seemed like maybe it was just some procedures that didn’t really make sense as objects. As I’ve been reading the section in Analysis Patterns on Posting Rules, I’ve been getting some ideas. Here’s what I had in mind. [Hands over a sketch]
图 11.9.在批处理中使用发布规则的示例
Figure 11.9. A shot at using Posting Rules in the batch
开发者1:这个“发布服务”是什么?
Developer 1: What is this “Posting Service”?
开发人员 2:这是一个FACADE,它公开会计应用程序的 API 并将其作为SERVICE呈现。实际上,我之前已经创建了它,以简化批处理代码,它还为我提供了一个用于发布到遗留系统的INTENTION-REVEALING INTERFACE。
Developer 2: That is a FACADE that exposes the accounting application’s API and presents it as a SERVICE. I actually made that a while back to simplify the batch code, and it also gave me an INTENTION-REVEALING INTERFACE for posting to the legacy system.
开发人员 1:有趣。那么,您打算对这些发布规则使用哪种触发方式?
Developer 1: Interesting. So, which firing style do you plan to use for these Posting Rules?
开发人员 2:我实际上还没有走那么远。
Developer 2: I hadn’t really gotten that far.
开发人员 1: Eager Firing 适用于应计项目,因为批处理实际上告诉资产插入它们,但它不适用于白天输入的付款。
Developer 1: Eager Firing would work for Accruals, since the batch actually tells the Asset to insert them, but it wouldn’t work for Payments, which get entered during the day.
开发人员 2:我认为我们无论如何都不想将计算方法与批处理紧密结合。如果我们决定在不同时间触发利息计算,就会把事情搞乱。从概念上讲,这似乎不太正确。
Developer 2: I don’t think we would want to couple the calculation method that tightly to the batch anyway. If we ever decided to trigger interest calculations at a different time, it would mess things up. And it just doesn’t seem right, conceptually.
开发人员 1:听起来像是基于发布规则的触发。批处理通知每个发布规则执行,规则会查找合适的新条目,然后执行其操作。这几乎就是您绘制的方式。
Developer 1: It sounds like Posting-Rule-based firing. The batch tells each Posting Rule to execute, and the rule goes and looks for appropriate new Entries and then does its thing. That’s pretty much the way you’ve drawn it.
开发人员 2:这样,我们就避免了在批处理设计上产生大量依赖,并且批处理保持控制。听起来是对的。
Developer 2: So then we avoid creating a lot of dependencies on the batch design, and the batch keeps control. That sounds right.
开发人员 1:我对这些对象与帐户和分录的交互仍然有点模糊。
Developer 1: I’m still a little vague on the interaction of these objects with the Accounts and Entries.
开发人员 2:你和我都是。书中的示例在帐户和过账规则之间建立了直接联系。这有点合乎逻辑,但我认为它对我们来说效果不太好。我们每次都必须从数据中实例化这些对象,因此我们必须弄清楚适用哪条规则才能将其关联起来。同时,资产对象知道每个帐户的内容,因此知道要应用哪条规则。无论如何,剩下的部分呢?
Developer 2: You and me both. The examples in the book create a direct link between the Accounts and the Posting Rules. That is kind of logical, but I don’t think it will work very well for us. We have to instantiate these objects from data each time, so we would have to figure out which rule applies in order to associate it. Meanwhile, the Asset object is the one that knows the content of each Account, and therefore which rule to apply. Anyway, what about the rest of this?
开发人员 1:我不想吹毛求疵,但我认为我们使用“方法”的方式不对。我认为这个概念是方法计算要过账的金额,比如说,收入中预扣的 20% 税款。但在我们的例子中,这很简单:总是过账全额。我认为过账规则本身应该知道要过账到哪个账户,这对应于我们的“分类账名称”。
Developer 1: I hate to nitpick, but I don’t think that we’re using “Method” right. I think the concept is that the Method computes the amount to be posted—like, say, a 20 percent tax with-holding on income. But in our case, that’s simple: it’s always the full amount being posted. I think the Posting Rule itself is supposed to know which Account to post to, which corresponds to our “ledger name.”
开发人员 2:哦。所以如果过账规则负责了解正确的分类账名称,我们可能根本不需要方法。
Developer 2: Oh. So if the Posting Rule is responsible for knowing the correct ledger name, we probably don’t need Method at all.
实际上,选择正确的账本名称的整个过程变得越来越复杂。它已经是收入类型(费用或利息)与“资产类别”(企业应用于每项资产的类别)的结合。我希望这个新模型能在这方面有所帮助。
Actually, this whole business of choosing the right ledger name is getting more and more complicated. It is already a combination of the type of income (fee or interest) with the “asset class” (a category the business applies to each Asset). That is one place I’m hoping this new model will help.
开发人员 1:好的,让我们集中精力。过账规则负责根据帐户的属性选择分类账。目前,我们可以让它成为一种处理资产类别和利息与费用之间区别的直接方法。将来,您将拥有一个可以增强的对象模型,以处理更复杂的情况。
Developer 1: OK, let’s focus there. The Posting Rule is responsible for choosing the ledger based on attributes of the Account. For now, we can make it a straightforward way to handle asset class and the distinction between interest and fees. In the future, you’ll have an OBJECT MODEL you can enhance to handle more complex cases.
开发人员 2:我需要再考虑一下。让我仔细考虑一下,重新阅读一下模式,然后再尝试一下。明天下午我可以再和你讨论一下吗?
Developer 2: I need to think about this some more. Let me mull it over, and reread the patterns, and then I’ll take another stab at it. Could I talk with you about this again tomorrow afternoon?
在接下来的几天里,两位开发人员制定了一个模型并重构了代码,以便批处理只需遍历 Assets ,向每个 Assets 发送一些不言自明的消息,然后提交数据库事务。复杂性被转移到领域层,其中对象模型使其更加明确和抽象。
Over the next few days, the two developers worked out a model and refactored the code so that the batch simply iterated through the Assets, sending a few self-explanatory messages to each and then committing the database transactions. The complexity was shifted into the domain layer, where an object model made it both more explicit and more abstract.
图 11.10. 带有发布规则的类图
Figure 11.10. The class diagram with Posting Rules
图 11.11. 显示规则触发的序列图
Figure 11.11. Sequence diagram showing rule firing
开发人员与分析模式中提出的模型细节有很大不同,但他们觉得自己保留了概念的精髓。他们对将资产纳入发布规则的选择感到有点不舒服。他们之所以这样做,是因为Asset知道每个Account的性质(费用或利息),并且也是脚本的自然访问点。要将规则对象直接与 Account 关联,就需要在每次实例化对象时(每次运行批处理时)与Asset对象进行协作。相反,他们让Asset对象通过其SINGLETON访问查找两个相关规则,并向它们传递适当的Account。这似乎使代码更加直接,因此他们做出了务实的决定。
The developers departed considerably from the details of the models presented in Analysis Patterns, yet they felt they had preserved the essence of the concepts. They were a little uncomfortable about involving the Asset in the selection of the Posting Rule. They went that way because the Asset had the knowledge of the nature of each Account (fee or interest) and was also the natural access point for the script. To have associated the rule object directly with the Account would have required a collaboration with the Asset object on each instantiation of the objects (each time the batch was run). Instead, they let the Asset object look up the two relevant rules through their SINGLETON access and pass them the appropriate Account. It seemed to make the code much more direct and so they made a pragmatic decision.
他们都认为,从概念上讲,最好只将过账规则与科目关联起来,同时让资产专注于产生应计项目。他们希望后续的重构和更深入的洞察能让他们回到这一点,并向他们展示一种在不失去代码明显性的情况下进行这种清晰划分的方法。
They both felt that conceptually it would have been better to associate Posting Rules only with Accounts, while keeping the Asset focused on its job of generating Accruals. They hoped that subsequent refactorings and deeper insight would bring them back to this and show them a way to make this clean division without losing the obviousness of the code.
当您有幸拥有分析模式时,它几乎从来不会满足您的特定需求。但它为您的调查提供了有价值的线索,并提供了清晰抽象的词汇。它还应该为您提供实施后果方面的指导,让您免于日后的痛苦。
When you are lucky enough to have an analysis pattern, it hardly ever is the answer to your particular needs. Yet it offers valuable leads in your investigation, and it provides cleanly abstracted vocabulary. It should also give you guidance about implementation consequences that will save you pain down the road.
所有这些都为知识处理和重构提供了动力,使人们能够获得更深入的见解,并促进发展。结果通常类似于分析模式中记录的形式,但会根据情况进行调整。有时结果甚至与分析模式本身没有明显关联,但却受到模式见解的刺激。
All this feeds into the dynamo of knowledge crunching and refactoring toward deeper insight and stimulates development. The result often resembles the form documented in the analysis pattern, but adapted to circumstances. Sometimes the result doesn’t even obviously relate to the analysis pattern itself, yet was stimulated by the insights from the pattern.
有一种变化是你应该避免的。当你使用一个众所周知的分析模式中的术语时,无论表面形式有多大变化,都要注意保持它所指的基本概念不变。这样做有两个原因。首先,该模式可能嵌入了有助于你避免问题的理解。其次,更重要的是,当你的UBIQUITOUS LANGUAGE包含被广泛理解或至少很好地理解的术语时,它会得到增强解释。如果您的模型定义随着模型的自然演化而改变,请花点功夫更改名称。
There is one kind of change you should avoid. When you use a term from a well-known analysis pattern, take care to keep the basic concept it designates intact, however much the superficial form might change. There are two reasons for this. First, the pattern may embed understanding that will help you avoid problems. Second, and more important, your UBIQUITOUS LANGUAGE is enhanced when it includes terms that are widely understood or at least well explained. If your model definitions change through the natural evolution of the model, take the trouble to change the names too.
已经有很多关于对象模型的文章,有些专门针对某个行业中的一种应用,有些则相当通用。它们中的大多数提供了一个想法的种子,但只有少数几个抓住了选择背后的原因以及随之而来的后果,而这些正是分析模式中最有用的部分。更多这样的精炼分析模式将是有价值的,可以帮助我们避免一次又一次地重新发明轮子。我很惊讶能看到一个全面的目录,但可能会出现特定于行业的目录。而且,一些跨许多应用程序的领域的模式可以广泛共享。
Quite a lot of object models have been written about, some specialized for one kind of application in one industry and some quite general. Most of them provide the seed of an idea, but only a few have captured the reasoning behind the choices and the consequences that follow, which are the most useful parts of an analysis pattern. More of these refined analysis patterns would be valuable, to help save us from reinventing the wheel again and again. I’d be surprised ever to see a comprehensive catalog, but industry-specific catalogs might arise. And patterns for some domains that cross many applications could be widely shared.
这种有组织知识的重新应用与通过框架或组件重用代码的尝试完全不同,只不过两者都可能提供一个不明显的想法的种子。模型,即使是通用框架,也是一个完整的工作整体,而分析则是模型片段的工具包。分析模式专注于最关键和最困难的决策,并阐明替代方案和选择。它们预测了后续后果,如果您必须自己发现这些后果,代价将非常高昂。
This kind of reapplication of organized knowledge is completely different from attempts to reuse code through frameworks or components, except that either could provide the seed of an idea that is not obvious. A model, even a generalized framework, is a complete working whole, while an analysis is a kit of model fragments. Analysis patterns focus on the most critical and difficult decisions and illuminate alternatives and choices. They anticipate downstream consequences that are expensive if you have to discover them for yourself.
到目前为止,本书探讨的模式专门用于在模型驱动设计的背景下解决领域模型中的问题。但实际上,迄今为止发布的大多数模式都更注重技术。设计模式和领域模式有什么区别?首先,开创性著作《设计模式》的作者是这样说的:
The patterns explored in this book so far are intended specifically for solving problems in a domain model in the context of a MODEL-DRIVEN DESIGN. Actually, though, most of the patterns published to date are more technical in focus. What is the difference between a design pattern and a domain pattern? For starters, the authors of the seminal book, Design Patterns, had this to say:
观点会影响一个人对什么是模式、什么不是模式的理解。一个人的模式可能是另一个人的基本构建块。在本书中,我们专注于某一抽象级别的模式。设计模式不是诸如链接列表和哈希表之类的设计,这些设计可以编码在类中并按原样重用。它们也不是针对整个应用程序或子系统的复杂、特定领域的设计。本书中的设计模式是对通信对象和类的描述,这些对象和类是为解决特定环境中的一般设计问题而定制的。[ Gamma 等,1995 年,第 3 页]
Point of view affects one’s interpretation of what is and isn’t a pattern. One person’s pattern can be another person’s primitive building block. For this book we have concentrated on patterns at a certain level of abstraction. Design patterns are not about designs such as linked lists and hash tables that can be encoded in classes and reused as is. Nor are they complex, domain-specific designs for an entire application or subsystem. The design patterns in this book are descriptions of communicating objects and classes that are customized to solve a general design problem in a particular context. [Gamma et al. 1995, p. 3]
设计模式中的部分(并非全部)模式可用作领域模式。这样做需要改变重点。设计模式提供了一系列设计元素,这些元素解决了各种情况下经常遇到的问题。这些模式的动机和模式本身都是以纯技术术语呈现的。但这些元素的子集可以应用于领域建模和设计的更广泛的背景,因为它们对应于许多领域中出现的一般概念。
Some, not all, of the patterns in Design Patterns can be used as domain patterns. Doing so requires a shift in emphasis. Design Patterns presents a catalog of design elements that have solved problems commonly encountered in a variety of contexts. The motivations of these patterns and the patterns themselves are presented in purely technical terms. But a subset of these elements can be applied in the broader context of domain modeling and design, because they correspond to general concepts that emerge in many domains.
除了设计模式中的模式外,多年来还提出了许多其他技术设计模式。其中一些模式对应于领域中出现的深层概念。借鉴这项工作会很好。为了在领域驱动设计中利用这些模式,我们必须同时从两个层面看待这些模式。在一个层面上,它们是代码中的技术设计模式。在另一个层面上,它们是模型中的概念模式。
In addition to those in Design Patterns, there have been many other technical design patterns presented over the years. Some of them correspond to deep concepts that emerge in domains. It would be nice to draw on this work. To make use of such patterns in domain-driven design, we have to look at the patterns on two levels simultaneously. On one level, they are technical design patterns in the code. On the other level, they are conceptual patterns in the model.
设计模式中的特定模式示例将展示如何将设计模式应用于领域模型,并阐明技术设计模式和领域模式之间的区别。COMPOSITE和STRATEGY展示了如何通过以不同的方式思考一些经典设计模式,将其应用于领域问题。...
A sample of specific patterns from Design Patterns will show how a pattern conceived as a design pattern can be applied in the domain model, and it will clarify the distinction between a technical design pattern and a domain pattern. COMPOSITE and STRATEGY demonstrate how some of the classic design patterns can be applied to domain problems by thinking about them in a different way. . . .
定义一系列算法,封装每个算法,并使它们可互换。策略使算法可以独立于使用它的客户端而变化。[ Gamma 等 1995 ]
Define a family of algorithms, encapsulate each one, and make them interchangeable. STRATEGY lets the algorithm vary independently from clients that use it. [Gamma et al. 1995]
领域模型包含一些在问题领域中没有技术动机但实际上有意义的流程。当必须提供替代流程时,选择适当流程的复杂性与多个流程本身的复杂性相结合,事情就会变得无法控制。
Domain models contain processes that are not technically motivated but actually meaningful in the problem domain. When alternative processes must be provided, the complexity of choosing the appropriate process combines with the complexity of the multiple processes themselves, and things get out of hand.
当我们对流程进行建模时,我们经常会意识到,有多种合法的方法来执行流程。当我们开始描述这些选项时,我们对流程的定义变得笨拙而复杂。我们选择的实际行为替代方案被掩盖了,因为它们与其他行为混杂在一起。
When we model processes, we often realize that there is more than one legitimate way of doing them. As we start to describe these options, our definition of the process becomes clumsy and complicated. The actual behavioral alternatives we are choosing between are obscured as they are mixed in with the rest of the behavior.
我们希望将这种变化与流程的主要概念分开。这样我们就能更清楚地看到主要流程和选项。STRATEGY模式已经在软件设计社区中得到很好的确立,它解决了这个问题,尽管重点是技术性的。在这里,它被应用为模型中的概念,并反映在该模型的代码实现中。同样需要将流程中高度可变的部分与更稳定的部分分离开来。
We would like to separate this variation from the main concept of the process. Then we would be able to see both the main process and the options more clearly. The STRATEGY pattern, already well established in the software design community, addresses this very issue, though the focus is technical. Here it is being applied as a concept in a model and reflected in the code implementation of that model. There is the same need to decouple the highly variable part of the process from the more stable part.
将流程的不同部分分解为模型中单独的“策略”对象。将规则和其控制的行为分开。按照策略设计模式实施规则或可替代流程。策略对象的多个版本代表可以完成流程的不同方式。
Factor the varying part of a process into a separate “strategy” object in the model. Factor apart a rule and the behavior it governs. Implement the rule or substitutable process following the STRATEGY design pattern. Multiple versions of the strategy object represent different ways the process can be done.
尽管传统观点将STRATEGY视为一种设计模式,侧重于替代不同算法的能力,但将其用作领域模式则侧重于表达概念(通常是一个流程或一条策略规则)的能力。
Whereas the conventional view of STRATEGY as a design pattern focuses on the ability to substitute different algorithms, its use as a domain pattern focuses on its ability to express a concept, usually a process or a policy rule.
路线规范被传递给路线服务,其工作是构建满足规范的详细行程。此服务是一个优化引擎,可以进行调整以找到最快路线或最便宜的路线。
A Route Specification is being passed to a Routing Service, whose job is to construct a detailed Itinerary that satisfies the SPECIFICATION. This SERVICE is an optimization engine that can be tuned to find either the fastest route or the cheapest one.
图 12.1.带有选项的SERVICE接口将需要条件逻辑。
Figure 12.1. A SERVICE interface with options will need conditional logic.
这种设置看起来不错,但仔细查看路由代码会发现每次计算都有条件,导致最快或最便宜之间的决定到处都是。当添加新标准以在路线之间做出更微妙的选择时,麻烦就会随之而来。
This setup looks OK, but a detailed look at the routing code would reveal conditionals in every computation, making the decision between fastest or cheapest appear all over the place. More trouble will come when new criteria are added to make more subtle choices between routes.
一种方法是将这些调整参数分离到STRATEGIES中。然后可以明确表示它们,并将其作为参数传递到路由服务中。
One approach is to separate those tuning parameters into STRATEGIES. Then they can be represented explicitly, passed into the Routing Service as a parameter.
路由服务现在以相同、无条件的方式处理所有请求,寻找具有低幅度的航段序列(由航段幅度策略计算得出) 。
The Routing Service now handles all requests in the same, unconditional way, looking for a sequence of Legs with a low magnitude, as computed by the Leg Magnitude Policy.
这种设计具有激发“设计模式”中策略模式的优点。在应用程序多功能性和灵活性级别上,现在可以通过安装适当的航段量级策略来控制和扩展路由服务的行为。图 12.2中所示的策略(最快或最便宜)只是最明显的策略。平衡速度和成本的组合是可能的。可能还有其他因素,例如倾向于在公司自己的运输工具上预订货物,而不是分包给其他船运公司的运输工具。这些修改可以在不诉诸策略的情况下进行,但逻辑会蜿蜒穿过路由服务的内部并使其接口臃肿。解耦确实使其清晰且易于测试。
This design has the advantages that motivate the STRATEGY pattern in Design Patterns. On the level of application versatility and flexibility, the behavior of the Routing Service can now be controlled and extended by installing an appropriate Leg Magnitude Policy. The STRATEGIES illustrated in Figure 12.2 (fastest or cheapest) are only the most obvious ones. Combinations that balance speed and cost are likely. There may be other factors altogether, such as a bias toward booking cargo on the company’s own transports rather than subcontracting to carry them on the transports of other shipping companies. These modifications could have been made without resorting to STRATEGIES, but the logic would have wound through the internals of the Routing Service and bloated its interface. The decoupling does make it clear and easily testable.
图 12.2. 通过作为参数传递的STRATEGY(POLICY)选择确定的选项
Figure 12.2. Options determined by choice of STRATEGY (POLICY) passed as argument
领域中一个非常重要的规则,即在构建行程时选择一条行程而不是另一条行程的基础,现在变得明确而独特。它传达了这样的知识:单个行程的特定属性(可能派生)归结为一个数字,是路线的基础。这使得在领域语言中定义路线服务行为的简单语句成为可能:路线服务选择具有最低根据所选的策略确定腿部的总体大小。
A fundamentally important rule in the domain, the basis of choosing one Leg over another when building an Itinerary, is now explicit and distinct. It conveys the knowledge that a specific attribute (potentially derived) of an individual leg, boiled down to a single number, is the basis for routing. This makes possible a simple statement in the language of the domain that defines the Routing Service’s behavior: The Routing Service chooses an Itinerary with a minimum total magnitude of the Legs based on the chosen STRATEGY.
注意:此讨论意味着Routing Service在搜索Itinerary时实际上正在评估Legs 。此方法在概念上很简单,并且可以实现合理的原型实现,但效率可能低得令人无法接受。此应用将在第 14 章“维护模型完整性”中再次讨论,其中将使用相同的接口和完全不同的Routing Service实现。
Note: This discussion implies that the Routing Service is actually evaluating Legs as it searches for an Itinerary. This approach is conceptually straightforward, and it could make a reasonable prototype implementation, but it is probably unacceptably inefficient. This application will be taken up again in Chapter 14, “Maintaining Model Integrity,” where the same interface will be used with a completely different implementation of the Routing Service.
当我们在领域层使用技术设计模式时,我们必须添加额外的动机,即另一层含义。当 STRATEGY对应于实际的业务策略或政策时,该模式就不仅仅是一种有用的实现技术(尽管就其本身而言,这也是有价值的)。
When we use the technical design pattern in the domain layer, we have to add an additional motivation, another layer of meaning. When the STRATEGY corresponds to an actual business strategy or policy, the pattern becomes more than just a useful implementation technique (though that too is valuable as far as it goes).
设计模式的后果完全适用。例如,在《设计模式》中,Gamma 等人指出客户端必须了解不同的STRATEGIES,这也是一个建模问题。纯粹从实现上考虑,STRATEGIES可能会增加应用程序中的对象数量。如果这是一个问题,可以通过将STRATEGIES实现为上下文可以共享的无状态对象来减少开销。 《设计模式》中对实现方法的广泛讨论都适用于此。这是因为我们仍在使用 STRATEGY 。我们的动机部分不同,这会影响一些选择,但设计模式中嵌入的经验可供我们支配。
The consequences of the design pattern fully apply. For example, in Design Patterns, Gamma et al. point out that clients must be aware of different STRATEGIES, which is also a modeling concern. A concern purely of implementation is that STRATEGIES can increase the number of objects in the application. If that is a problem, the overhead can be reduced by implementing STRATEGIES as stateless objects that contexts can share. The extensive discussion of implementation approaches in Design Patterns all applies here. This is because we are still using a STRATEGY. Our motivations are partially different, which will affect some choices, but the experience embedded in the design pattern is at our disposal.
将对象组合成树形结构,以表示部分-整体层次结构。C OMPOSITE让客户端可以统一处理单个对象和对象组合。[ Gamma 等 1995 ]
Compose objects into tree structures to represent part-whole hierarchies. COMPOSITE lets clients treat individual objects and compositions of objects uniformly. [Gamma et al. 1995]
在对复杂领域进行建模时,我们经常会遇到一个由多个部分组成的重要对象,而这些部分又由多个部分组成,有时甚至嵌套到任意深度。在某些领域,每个层次在概念上都是不同的,但在其他情况下,从某种意义上说,这些部分与整体是同一类东西,只是规模较小。
We often encounter, while modeling complex domains, an important object that is composed of parts, which are themselves made up of parts, which are made up of parts—occasionally even nesting to arbitrary depth. In some domains, each of these levels is conceptually distinct, but in other cases, there is a sense in which the parts are the same kind of thing as the whole, only smaller.
当嵌套容器的关联性未反映在模型中时,必须在层次结构的每个级别上重复常见行为,并且嵌套是固定的(例如,容器通常不能在自己的级别包含其他容器,并且级别数是固定的)。客户端必须通过不同的接口处理层次结构的不同级别,即使它们可能不关心概念上的差异。通过层次结构进行递归以生成聚合信息非常复杂。
When the relatedness of nested containers is not reflected in the model, common behavior has to be duplicated at each level of the hierarchy, and nesting is rigid (for example, containers can’t usually contain other containers at their own level, and the number of levels is fixed). Clients must deal with different levels of the hierarchy through different interfaces, even though there may be no conceptual difference they care about. Recursion through the hierarchy to produce aggregated information is very complicated.
在领域中应用任何设计模式时,首先要考虑的是模式理念是否真的适合领域概念。通过一些关联对象进行递归移动可能很方便,但是否存在真正的整体-部分层次结构?您是否找到了一种抽象,在这种抽象下,所有部分都是真正的整体。您是否希望模型具有相同的概念类型?如果有,COMPOSITE将使模型的这些方面更加清晰,同时允许您利用设计模式中经过深思熟虑的设计和实现注意事项。
When applying any design pattern in the domain, the first concern should be whether the pattern idea really is a good fit for the domain concept. It might be convenient to move recursively through some associated objects, but is there a true whole-part hierarchy? Have you found an abstraction under which all the parts truly are the same conceptual type? If you have, COMPOSITE will make those aspects of the model clearer, while allowing you to tap into the carefully thought-out design and implementation considerations of the design pattern.
所以:
Therefore:
定义一个包含 COMPOSITE 所有成员的抽象类型。在容器上实现返回信息的方法,以返回有关其内容的聚合信息。“叶”节点根据自己的值实现这些方法。客户端处理抽象类型,无需区分叶和容器。
Define an abstract type that encompasses all members of the COMPOSITE. Methods that return information are implemented on containers to return aggregated information about their contents. “Leaf” nodes implement those methods based on their own values. Clients deal with the abstract type and have no need to distinguish leaves from containers.
这是结构层面上相对明显的模式,但设计师通常不会强迫自己充实模式的操作层面。COMPOSITE 在每个结构层面上都提供相同的行为,并且可以对透明地反映其构成的小部件或大部件提出有意义的问题。严格的对称性是模式力量的关键。
This is a relatively obvious pattern on the structural level, but designers often do not push themselves to flesh out the operational level of the pattern. The COMPOSITE offers the same behavior at every structural level, and meaningful questions can be asked of small or large parts that transparently reflect their makeup. That rigorous symmetry is the key to the power of the pattern.
完整的货物运输路线非常复杂。首先,集装箱必须通过卡车运到铁路终点站,然后运往港口,再通过船舶运输到另一个港口,可能还要转运到其他船舶,最后通过陆路运输到另一端。
A complete cargo shipment route is complicated. First the container must be trucked to a railhead, then carried to a port, then transported on a ship to another port, possibly transferred to other ships, and finally transported by ground on the other end.
图 12.3。由“路段”组成的“路线”示意图
Figure 12.3. A schematic of a “route” made up of “legs”
应用程序开发团队创建了一个对象模型来表达这些组成一条路线的任意长的腿串。
An application development team has created an object model to express these arbitrarily long strings of legs that assemble into a route.
图 12.4.由路段组成的路线类图
Figure 12.4. A class diagram of a Route made up of Legs
使用此模型,开发人员可以根据预订请求创建路线对象。他们能够将航段处理到操作计划中,以便逐步处理货物。然后他们发现了一些东西。
Using this model, the developers are able to create Route objects based on booking requests. They are able to process the Legs into the operational plan for the step-by-step handling of the cargo. Then they discover something.
开发人员一直认为路线是任意的、未分化的一串航段。
The developers had always thought of a route as an arbitrary, un-differentiated string of legs.
图 12.5. 开发人员对路线的构想
Figure 12.5. The developers’ conception of a route
事实证明,领域专家将该路线视为五个逻辑段的序列。
It turns out the domain experts see the route as a sequence of five logical segments.
图 12.6. 业务专家对路线的构想
Figure 12.6. The business experts’ conception of a route
除其他事项外,这些子路线可能是由不同的人在不同时间规划的,因此必须将其视为不同的路线。仔细观察,你会发现“门腿”与其他行程则涉及当地租用的卡车甚至客户运输,这与精心安排的铁路和轮船运输形成对比。
Among other things, these subroutes may be planned at different times by different people, so they have to be viewed as distinct. And on closer inspection, the “door legs” are quite different from the other legs, involving locally hired trucks or even customer haulage, in contrast to the elaborately scheduled rail and ship transports.
反映所有这些区别的对象模型开始变得复杂。
An object model reflecting all these distinctions starts to get complicated.
图 12.7. Route 的详细类图
Figure 12.7. The elaborated class diagram of Route
从结构上看,这个模型还不错,但处理操作计划的一致性却丧失了,因此代码,甚至行为描述,都变得更加复杂。其他复杂情况也开始浮现。任何路线的遍历都涉及多个不同类型的对象集合。
Structurally the model isn’t so bad, but the uniformity of processing the operational plan is lost, so the code, or even a description of behavior, becomes much more complicated. Other complications begin to surface, too. Any traversal of a route involves multiple collections of different types of objects.
进入COMPOSITE。对于某些客户来说,最好将此构造中的不同级别统一视为由路线组成的路线。从概念上讲,这种观点是正确的。路线的每个级别都是集装箱从一个点到另一个点的移动,一直到单个航段。(参见图 12.8。)
Enter COMPOSITE. It would be nice, for certain clients, to treat the different levels in this construct uniformly, as routes made up of routes. Conceptually this view is sound. Every level of Route is a movement of a container from one point to another, all the way down to an individual leg. (See Figure 12.8.)
图 12.8. 使用COMPOSITE 的类图
Figure 12.8. A class diagram using COMPOSITE
现在,静态类图没有像前一个类图那样告诉我们门腿和其他部件如何组合在一起。但该模型不仅仅是一个静态类图。我们将通过其他图表(参见图 12.9 )和(现在简单得多的)代码传达组装信息。该模型捕获了所有这些不同类型的“路线”之间的深层关联。生成操作计划再次变得简单,其他路线遍历操作也一样简单。
Now, the static class diagram does not tell us as much about how door legs and other segments fit together as the previous one did. But the model is more than a static class diagram. We’ll convey assembly information through other diagrams (see Figure 12.9) and through the (now much simpler) code. This model captures the deep relatedness of all these different kinds of “Route.” Generating the operational plan is simple again, as are other route-traversing operations.
图 12.9. 表示完整路线的实例
Figure 12.9. Instances representing a complete Route
一条路线由其他路线组成,首尾相连,从一个地方到达另一个地方,您可以实现不同细节的路线。您可以切断路线的末端并拼接新的末端,您可以任意嵌套细节,您可以利用各种可能有用的选项。
With a route made of other routes, pieced together end to end to get from one place to another, you can have route implementations of varying detail. You can chop off the end of a route and splice on a new ending, you can have arbitrary nesting of detail, and you can exploit all sorts of possibly useful options.
当然,我们目前还不需要这样的选项。在我们需要这些路线段和不同的门腿之前,没有COMPOSITE我们也做得很好。设计模式应该只在需要时才应用。
Of course, we don’t yet need such options. And before we needed those route segments and distinct door legs, we were doing just fine without COMPOSITE. A design pattern should be applied only when it is needed.
因为我之前提到过FLYWEIGHT模式(第 5 章),所以你可能认为它是应用于领域模型的模式示例。事实上,FLYWEIGHT是一个与领域模型没有对应关系的设计模式的很好示例。
Because I referred to the FLYWEIGHT pattern earlier (in Chapter 5), you might have assumed that it is an example of a pattern to be applied to domain models. In fact, FLYWEIGHT is a good example of a design pattern that has no correspondence to the domain model.
当一组有限的VALUE OBJECTS被多次使用时(例如房屋平面图中的电源插座),将它们实现为FLYWEIGHTS可能更有意义。这是VALUE OBJECTS可用的实现选项,但不适用于ENTITIES。这与COMPOSITE形成对比,其中概念对象由其他概念对象组成。在这种情况下,该模式适用于模型和实现,这是领域模式的基本特征。
When a limited set of VALUE OBJECTS is used many times (as in the example of electrical outlets in a house plan), it may make sense to implement them as FLYWEIGHTS. This is an implementation option available for VALUE OBJECTS and not for ENTITIES. Contrast this with COMPOSITE, in which conceptual objects are composed of other conceptual objects. In that case, the pattern applies to both model and implementation, which is an essential trait of a domain pattern.
我不会试图编制一份可用作领域模式的设计模式列表。虽然我想不出使用解释器作为领域模式的例子,但我并不准备说没有任何领域概念可以适用。唯一的要求是,模式应该对概念领域有所说明,而不仅仅是技术问题的技术解决方案。
I’m not going to try to compile a list of the design patterns that can be used as domain patterns. Although I can’t think of an example of using an interpreter as a domain pattern, I’m not prepared to say that there is no conception of any domain that would fit. The only requirement is that the pattern should say something about the conceptual domain, not just be a technical solution to a technical problem.
重构以获得更深入的洞察力是一个多方面的过程。停下来思考一下要点会很有帮助。你必须关注三件事。
Refactoring toward deeper insight is a multifaceted process. It will be helpful to stop for a moment to pull together the major points. There are three things you have to focus on.
1.生活在域内。
1. Live in the domain.
2.继续用不同的方式看待事物。
2. Keep looking at things a different way.
3.与领域专家保持不间断的对话。
3. Maintain an unbroken dialog with domain experts.
寻求对领域的洞察为重构过程创建了更广泛的背景。
Seeking insight into the domain creates a broader context for the process of refactoring.
经典的重构场景包括一两个开发人员坐在键盘前,意识到某些代码可以改进,然后即时更改它(当然,使用单元测试来验证其结果)。这种做法应该一直发生,但这不是全部。
The classic refactoring scenario involves a developer or two sitting at the keyboard, recognizing that some code can be improved, and then changing it on the fly (with unit tests to verify their results, of course). This practice should happen all the time, but it isn’t the whole story.
前五章提出了重构的扩展视图,叠加在传统的微重构方法上。
The previous five chapters present an expanded view of refactoring, superimposed on the conventional micro-refactoring approach.
重构可以以多种方式开始,以深入了解代码。这可能是对代码中某个问题的回应——一些复杂性或尴尬。与其应用代码的标准转换,不如开发人员感觉到问题的根源在于领域模型。也许缺少某个概念。也许某些关系是错误的。
Refactoring toward deeper insight can begin in many ways. It may be a response to a problem in the code—some complexity or awkwardness. Rather than apply a standard transformation of the code, the developers sense that the root of the problem is in the domain model. Perhaps a concept is missing. Maybe some relationship is wrong.
与传统的重构观点不同,当代码看起来很整洁、模型语言似乎与领域专家脱节,或者新需求无法自然融入时,也会出现同样的认识。重构可能源于学习,因为对重构有了更深入理解的开发人员看到了一个更清晰或更有用的模型的机会。
In a departure from the conventional view of refactoring, this same realization could come when the code looks tidy, if the language of the model seems disconnected from the domain experts, or if new requirements are not fitting in naturally. Refactoring might result from learning, as a developer who has gained deeper understanding sees an opportunity for a more lucid or useful model.
发现问题点往往是最困难和最不确定的部分。之后,开发人员可以系统地寻找新模型的元素。他们可以与同事和领域专家集思广益。他们可以利用以分析模式或设计模式形式编写的系统化知识。
Seeing the trouble spot is often the hardest and most uncertain part. After that, developers can systematically seek out the elements of a new model. They can brainstorm with colleagues and domain experts. They can draw on systematized knowledge written as analysis patterns or design patterns.
无论不满的根源是什么,下一步就是寻求一种改进方法,使模型能够清晰自然地传达信息。这可能只需要一些适度的改变,这些改变是立竿见影的,可以在几个小时内完成。在这种情况下,这种改变类似于传统的重构。但寻找新的模型可能需要更多的时间和更多人的参与。
Whatever the source of dissatisfaction, the next step is to seek a refinement that will make the model communicate clearly and naturally. This might require only some modest change that is immediately evident and can be accomplished in a few hours. In that case, the change resembles traditional refactoring. But the search for a new model may well call for more time and the involvement of more people.
变革的发起者会挑选几位擅长思考此类问题、了解该领域或具有强大建模技能的开发人员。如果存在细微差别,他们会确保领域专家参与其中。这个由四五个人组成的小组会去会议室或咖啡店,花半小时到一个半小时进行头脑风暴。他们绘制 UML 图;尝试使用这些对象演示场景。他们确保主题专家理解该模型并认为它有用。当他们找到满意的东西时,他们会回去编写代码。或者他们决定仔细考虑几天,然后回去做其他事情。几天后,小组重新开会,再次进行练习。这一次,他们更加自信,因为他们之前的想法被搁置了,他们得出了一些结论。他们回到电脑前,编写新的设计。
The initiators of the change pick a couple of other developers who are good at thinking through that kind of problem, who know that area of the domain, or who have strong modeling skills. If there are subtleties, they make sure a domain expert is involved. This group of four or five people goes to a conference room or a coffee shop and brainstorms for half an hour to an hour and a half. They sketch UML diagrams; they try walking through scenarios using the objects. They make sure the subject matter expert understands the model and finds it useful. When they find something they are happy with, they go back and code it. Or they decide to mull it over for a few days, and they go back and work on something else. A couple of days later, the group reconvenes and goes through the exercise again. This time they are more confident, having slept on their earlier thoughts, and they reach some conclusions. They go back to their computers and code the new design.
There are a few keys to keeping this process productive.
•自决。可以临时组建一个小团队来探索设计问题。团队可以运作几天然后解散。无需长期、复杂的组织结构。
• Self-determination. A small team can be assembled on the fly to explore a design problem. The team can operate for a few days and then disband. There is no need for long-term, elaborate organizational structures.
•范围和睡眠。几天内召开两到三次简短会议应该可以产生一个值得尝试的设计。拖延无济于事。如果你陷入困境,可能是因为你一次承担了太多任务。选择设计中较小的方面并专注于此。
• Scope and sleep. Two or three short meetings spaced out over a few days should produce a design worth trying. Dragging it out doesn’t help. If you get stuck, you may be taking on too much at once. Pick a smaller aspect of the design and focus on that.
•练习通用语言。让其他团队成员(尤其是主题专家)参与头脑风暴会议,为练习和改进通用语言创造了机会。最终的结果是改进了该语言,原始开发人员将收回该语言并将其形式化为代码。
• Exercising the UBIQUITOUS LANGUAGE. Involving the other team members—particularly the subject matter expert—in the brainstorming session creates an opportunity to exercise and refine the UBIQUITOUS LANGUAGE. The end result of the effort is a refinement of that LANGUAGE which the original developer(s) will take back and formalize in code.
本书前面的章节介绍了开发人员和领域专家探讨更好模型的几次对话。一次全面的头脑风暴会议是动态的、非结构化的,而且非常高效。
Earlier chapters in this book have presented several dialogs in which developers and domain experts probe for better models. A full-blown brainstorming session is dynamic, unstructured, and incredibly productive.
并不总是需要重新发明轮子。集思广益寻找缺失的概念和更好的模型的过程具有很大的能力,可以吸收来自任何来源的想法,将它们与本地知识相结合,并继续努力寻找当前情况的答案。
It isn’t always necessary to reinvent the wheel. The process of brainstorming for missing concepts and better models has a great capacity to absorb ideas from any source, combine them with local knowledge, and continue crunching to find answers to the current situation.
您可以从书籍和其他有关该领域本身的知识来源中获取想法。尽管该领域的人们可能没有创建适合运行软件的模型,但他们可能已经组织了概念并找到了一些有用的抽象。以这种方式提供知识处理过程可以产生更丰富、更快速的结果,这些结果对于领域专家来说可能也更熟悉。
You can get ideas from books and other sources of knowledge about the domain itself. Although the people in the field may not have created a model suitable for running software, they may well have organized the concepts and found some useful abstractions. Feeding the knowledge-crunching process this way leads to richer, quicker results that also will probably seem more familiar to domain experts.
有时,你可以以分析模式的形式借鉴他人的经验。这种输入具有阅读领域知识的一些效果,但在这种情况下,它专门针对软件开发,它应该直接基于您所在领域的软件实施经验。分析模式可以为您提供微妙的模型概念,并帮助您避免许多错误。但它们不会为您提供食谱。它们为知识处理过程提供信息。
Sometimes you can draw on the experience of others in the form of analysis patterns. This kind of input has some of the effect of reading about the domain, but in this case it is geared specifically toward software development, and it should be based directly on experience implementing software in your domain. Analysis patterns can give you subtle model concepts and help you avoid lots of mistakes. But they don’t give you a cookbook recipe. They feed the knowledge-crunching process.
在将各个部分组合在一起时,必须同时处理模型问题和设计问题。同样,这并不意味着从头开始发明一切。当设计模式既符合实现需求又符合模型概念时,通常可以在领域层中使用。
As the pieces are fit together, model concerns and design concerns must be dealt with in parallel. Again, it doesn’t always mean inventing everything from scratch. Design patterns can often be employed in the domain layer when they fit both an implementation need and the model concept.
同样,当一种常见的形式主义(如算术或谓词逻辑)适合某个领域的某个部分时,你可以将该部分分解出来并调整形式系统的规则。这提供了非常紧密且易于理解的模型。
Likewise, when a common formalism, such as arithmetic or predicate logic, fits some part of a domain, you can factor that part out and adapt the rules of the formal system. This provides very tight and readily understood models.
软件不只是为用户服务,也为开发者服务。开发者必须将代码与系统的其他部分集成。在迭代过程中,开发者一次又一次地更改代码。重构以获得更深入的洞察力既可以实现灵活的设计,也可以从中受益。
Software isn’t just for users. It’s also for developers. Developers have to integrate code with other parts of the system. In an iterative process, developers change the code again and again. Refactoring toward deeper insight both leads to and benefits from a supple design.
灵活的设计可以传达其意图。这种设计可以很容易地预测运行代码的效果,因此也很容易预测更改代码的后果。灵活的设计有助于限制心理负担,主要是通过减少依赖性和副作用。它基于域的深度模型,只在对用户最关键的地方进行细粒度处理。这使得在变化最常见的地方具有灵活性,而在其他地方则具有简单性。
A supple design communicates its intent. The design makes it easy to anticipate the effect of running code—and therefore it easy to anticipate the consequences of changing it. A supple design helps limit mental overload, primarily by reducing dependencies and side effects. It is based on a deep model of the domain that is fine-grained only where most critical to the users. This makes for flexibility where change is most common, and simplicity elsewhere.
如果你等到可以完全证明变更的合理性后才进行变更,那么你等得太久了。你的项目已经花费了巨额成本,而推迟的变更将更难进行,因为目标代码将更加复杂,并且更多地嵌入其他代码中。
If you wait until you can make a complete justification for a change, you’ve waited too long. Your project is already incurring heavy costs, and the postponed changes will be harder to make because the target code will have been more elaborated and more embedded in other code.
持续重构已被认为是一种“最佳实践”,但大多数项目团队对此仍过于谨慎。他们看到更改代码的风险和开发人员进行更改所花费的时间;但更难看到的是保留一个笨拙的设计的风险和围绕该设计进行工作的成本。想要重构的开发人员经常被要求证明这一决定的合理性。虽然这似乎是合理的,但它使本来就很困难的事情变得更加困难,并且往往会抑制重构(或将其推向地下)。软件开发并不是一个可预测的过程,因此无法准确计算出更改的好处或不进行更改的成本。
Continuous refactoring has come to be considered a “best practice,” but most project teams are still too cautious about it. They see the risk of changing code and the cost of developer time to make a change; but what’s harder to see is the risk of keeping an awkward design and the cost of working around that design. Developers who want to refactor are often asked to justify the decision. Although this seems reasonable, it makes an already difficult thing impossibly difficult, and tends to squelch refactoring (or drive it underground). Software development is not such a predictable process that the benefits of a change or the costs of not making a change can be accurately calculated.
重构以获得更深入的洞察力需要成为领域主题的持续探索、开发人员的教育以及开发人员和领域专家思想交流的一部分。因此,在以下情况下进行重构:
Refactoring toward deeper insight needs to become part of the ongoing exploration of the subject matter of the domain, the education of the developers, and the meeting of the minds of developers and domain experts. Therefore, refactor when
• 设计没有表达出团队当前对该领域的理解;
• The design does not express the team’s current understanding of the domain;
• 重要的概念隐含在设计中(并且您找到了一种使它们明确化的方法);或
• Important concepts are implicit in the design (and you see a way to make them explicit); or
• 您看到了让设计供应商提供一些重要部件的机会。
• You see an opportunity to make some important part of the design suppler.
这种激进的态度在任何时候都不能成为任何改变的理由。不要在发布前一天进行重构。不要引入“灵活的设计”,这些设计只是技术精湛的展示,但无法切中领域的核心。不要引入一个你无法说服领域专家使用的“更深层次的模型”,无论它看起来多么优雅。不要对事情太过绝对,而是要超越舒适区,朝着有利于重构的方向前进。
This aggressive attitude does not justify any change at any time. Don’t refactor the day before a release. Don’t introduce “supple designs” that are just demonstrations of technical virtuosity but fail to cut to the core of the domain. Don’t introduce a “deeper model” that you couldn’t convince a domain expert to use, no matter how elegant it seems. Don’t be absolute about things, but push beyond the comfort zone in the direction of favoring refactoring.
在查尔斯·达尔文提出进化论的一个多世纪里,标准的进化模型是物种随着时间的推移逐渐、相对稳定地变化。突然,在 20 世纪 70 年代,这一模型被“间断平衡”模型取代。在这种扩展的进化观中,长期的渐进变化或稳定被相对较短的快速变化所打断。然后,一切又回到了新的平衡状态。软件开发有一个有意的方向其背后缺乏进化(尽管在某些项目中可能并不明显),但尽管如此,它仍然遵循这种节奏。
For over a century after Charles Darwin introduced it, the standard model of evolution was that species changed gradually, somewhat steadily, over time. Suddenly, in the 1970s, this model was displaced by the “punctuated equilibrium” model. In this expanded view of evolution, long periods of gradual change or stability are interrupted by relatively short bursts of rapid change. Then things settle down into a new equilibrium. Software development has an intentional direction behind it that evolution lacks (although it may not be evident on some projects), but nonetheless it follows this kind of rhythm.
重构的传统描述听起来非常稳定。但为了获得更深入的洞察力而进行的重构通常并非如此。对模型进行一段时间的稳定改进可能会突然让你获得颠覆一切的洞察力。这些突破并不是每天都会发生,但导致深度模型和灵活设计的很大一部分变化都源于此。
Classical descriptions of refactoring sound very steady. Refactoring toward deeper insight usually isn’t. A period of steady refinement of a model can suddenly bring you to an insight that shakes up everything. These breakthroughs don’t happen every day, yet a large proportion of the changes that lead to a deep model and supple design emerge from them.
这种情况通常看起来不像是一个机会,而更像是一场危机。突然间,模型中出现了一些明显的不足。它所能表达的内容存在巨大漏洞,或者一些关键领域不透明。也许它做出的陈述是错误的。
Such a situation often does not look like an opportunity; it seems more like a crisis. Suddenly there is some obvious inadequacy in the model. There is a gaping hole in what it can express, or some critical area where it is opaque. Maybe it makes statements that are just wrong.
这意味着团队已经达到了一个新的理解水平。从他们现在提升的角度来看,旧模型看起来很糟糕。从这个角度来看,他们可以构思出一个更好的模型。
This means the team has reached a new level of understanding. From their now-elevated viewpoint, the old model looks poor. From that viewpoint, they can conceive a far better one.
重构以获得更深入的洞察力是一个持续的过程。隐含的概念被识别并明确化。设计的各个部分变得更加灵活,可能采用声明式风格。开发突然到达突破的边缘,并深入到深度模型——然后再次开始稳步改进。
Refactoring toward deeper insight is a continuing process. Implicit concepts are recognized and made explicit. Parts of the design are made suppler, perhaps taking on a declarative style. Development suddenly comes to the brink of a breakthrough and plunges through to a deep model—and then steady refinement starts again.
随着系统变得过于复杂,无法在单个对象的层面上完全了解,我们需要操纵和理解大型模型的技术。本书的这一部分介绍了使建模过程能够扩展到非常复杂领域的原则。大多数此类决策必须在团队层面做出,甚至在团队之间协商。这些决策往往是设计和政治相交叉的地方。
As systems grow too complex to know completely at the level of individual objects, we need techniques for manipulating and comprehending large models. This part of the book presents principles that enable the modeling process to scale up to very complicated domains. Most such decisions must be made at team level or even negotiated between teams. These are the decisions where design and politics often intersect.
最雄心勃勃的企业系统的目标是一个覆盖整个业务的紧密集成系统。然而,几乎任何此类组织的整个业务模型都过于庞大和复杂,无法作为一个单元进行管理,甚至无法理解。系统必须分解为更小的部分,无论是在概念上还是在实施上。挑战在于实现这种模块化,同时又不失去集成的好处,允许系统的不同部分进行互操作,以支持各种业务操作的协调。一个单一的、包罗万象的领域模型将难以操作,并且充满了微妙的重复和矛盾。一组通过临时接口粘合在一起的小型、不同的子系统将缺乏解决企业范围问题的能力,并且允许在每个集成点出现一致性问题。通过系统化、不断发展的设计策略可以避免这两种极端的陷阱。
The goal of the most ambitious enterprise system is a tightly integrated system spanning the entire business. Yet the entire business model for almost any such organization is too large and complex to manage or even understand as a single unit. The system must be broken into smaller parts, in both concept and implementation. The challenge is to accomplish this modularity without losing the benefits of integration, allowing different parts of the system to interoperate to support the coordination of various business operations. A monolithic, all-encompassing domain model will be unwieldy and loaded with subtle duplications and contradictions. A set of small, distinct subsystems glued together with ad hoc interfaces will lack the power to solve enterprise-wide problems and allows consistency problems to arise at every integration point. The pitfalls of both extremes can be avoided with a systematic, evolving design strategy.
即使在这种规模下,领域驱动设计也不会产生与实现无关的模型。每个决策都必须对系统开发产生直接影响,否则就无关紧要。战略设计原则必须指导设计决策,以减少各部分的相互依赖性并提高清晰度,而不会失去关键的互操作性和协同作用。他们必须专注于模型以捕捉系统的概念核心,即系统的“愿景”。他们必须在不拖累项目的情况下完成所有这些工作。为了帮助实现这些目标,第 IV 部分探讨了三个广泛的主题:背景、提炼和大规模结构。
Even at this scale, domain-driven design does not produce models unconnected to the implementation. Every decision must have a direct impact on system development, or else it is irrelevant. Strategic design principles must guide design decisions to reduce the interdependence of parts and improve clarity without losing critical interoperability and synergy. They must focus the model to capture the conceptual core of the system, the “vision” of the system. And they must do all this without bogging the project down. To help accomplish these goals, Part IV explores three broad themes: context, distillation, and large-scale structure.
上下文是所有原则中最不明显的,但实际上却是最基本的。一个成功的模型,无论大小,都必须始终保持逻辑一致,没有矛盾或重叠的定义。企业系统有时会集成不同来源的子系统,或者应用程序非常不同,以至于领域中很少有东西可以以相同的眼光看待。将这些不同部分中隐含的模型统一起来可能要求太高了。通过明确定义一个有界上下文 模型可以在其中应用,然后在必要时定义它与其他上下文的关系,建模者可以避免混淆模型。
Context, the least obvious of the principles, is actually the most fundamental. A successful model, large or small, has to be logically consistent throughout, without contradictory or overlapping definitions. Enterprise systems sometimes integrate subsystems with varying origins or have applications so distinct that very little in the domain is viewed in the same light. It may be asking too much to unify the models implicit in these disparate parts. By explicitly defining a BOUNDED CONTEXT within which a model applies and then, when necessary, defining its relationship with other contexts, the modeler can avoid bastardizing the model.
提炼可以减少混乱并适当地集中注意力。通常,大量的精力都花在了领域中的外围问题上。整体领域模型需要突出系统中最具增值性和特殊性的方面,并对其进行结构化以赋予该部分尽可能多的功能。虽然一些支持组件至关重要,但必须将它们放在适当的角度。这种关注不仅有助于将精力集中在系统的重要部分,而且还能防止系统愿景被遗忘。战略性提炼可以使大型模型更加清晰。有了更清晰的视角,核心域的设计就可以变得更加有用。
Distillation reduces the clutter and focuses attention appropriately. Often a great deal of effort is spent on peripheral issues in the domain. The overall domain model needs to make prominent the most value-adding and special aspects of your system and be structured to give that part as much power as possible. While some supporting components are critical, they must be put into their proper perspective. This focus not only helps to direct efforts toward vital parts of the system, but it keeps the vision of the system from being lost. Strategic distillation can bring clarity to a large model. And with a clearer view, the design of the CORE DOMAIN can be made more useful.
大规模结构使这幅图景更加完整。在非常复杂的模型中,你可能只见树木不见森林。提炼有助于将注意力集中在核心上,并将其他元素展示为配角,但如果没有一个总体主题,应用一些系统范围的设计元素和模式,这些关系仍然会过于混乱。我将概述几种大规模结构的方法,然后深入讨论其中一种模式,即责任层,以探讨使用这种结构的含义。所讨论的特定结构只是示例;它们不是一个全面的目录。应该根据需要发明新的结构,或者通过演变顺序的过程对其进行修改。一些这样的结构可以为设计带来统一性,从而加速开发并提高集成度。
Large-scale structure completes the picture. In a very complex model, you may not see the forest for the trees. Distillation helps, by focusing the attention on the core and presenting the other elements in their supporting roles, but the relationships can still be too confusing without an overarching theme, applying some system-wide design elements and patterns. I’ll give an overview of a few approaches to large-scale structure and then go into depth on one such pattern, RESPONSIBILITY LAYERS, to explore the implications of using such a structure. The specific structures discussed are only examples; they are not a comprehensive catalog. New ones should be invented as needed, or these should be modified, through a process of EVOLVING ORDER. Some such structure can bring a uniformity to the design that accelerates development and improves integration.
这三个原则单独使用时很有用,但结合在一起使用时则特别强大,有助于产生良好的设计——即使在没有人完全理解的庞大系统中也是如此。大规模结构为不同的部分带来一致性,以帮助这些部分相互配合。结构和提炼使各部分之间的复杂关系变得易于理解,同时又能保持全局观。有界上下文允许在不同部分进行工作,而不会破坏模型或无意中将其碎片化。将这些概念添加到团队的UBIQUITOUS LANGUAGE中有助于开发人员制定自己的解决方案。
These three principles, useful separately but particularly powerful taken together, help to produce good designs—even in a sprawling system that no one completely understands. Large-scale structure brings consistency to disparate parts to help those parts mesh. Structure and distillation make the complex relationships between parts comprehensible while keeping the big picture in view. BOUNDED CONTEXTS allow work to proceed in different parts without corrupting the model or unintentionally fragmenting it. Adding these concepts to the team’s UBIQUITOUS LANGUAGE helps developers work out their own solutions.
我曾经参与过一个项目,其中几个团队同时在开发一个重要的新系统。有一天,负责客户发票模块的团队准备实现一个名为Charge 的对象,这时他们发现另一个团队已经构建了一个对象。他们勤奋努力,着手重用现有对象。他们发现它没有“费用代码”,因此他们添加了一个。它已经具有他们需要的“已过账金额”属性。他们原本打算将其称为“应付金额”,但是 — 名字有什么意义呢? — 他们改了它。添加一些方法和关联后,他们得到了一些看起来像他们想要的东西,而没有干扰现有的东西。他们不得不忽略许多他们不需要的关联,但他们的应用程序模块可以运行。
I once worked on a project where several teams were working in parallel on a major new system. One day, the team working on the customer-invoicing module was ready to implement an object they called Charge, when they discovered that another team had already built one. Diligently, they set out to reuse the existing object. They discovered it didn’t have an “expense code,” so they added one. It already had the “posted amount” attribute they needed. They had been planning to call it “amount due,” but—what’s in a name?—they changed it. Adding a few more methods and associations, they got something that looked like what they wanted, without disturbing what was there. They had to ignore many associations they didn’t need, but their application module ran.
几天后,账单支付应用程序模块中出现了神秘问题,而最初编写该费用的模块就是为了这个目的。出现了一些奇怪的费用,没有人记得输入过,而且没有任何意义。当使用某些功能时,程序开始崩溃,尤其是当月的税务报告。调查显示,崩溃是在使用一个功能汇总当月所有付款的可扣除金额时发生的。神秘记录在“可扣除百分比”字段中没有值,尽管数据输入应用程序的验证需要它,甚至输入了默认值。
A few days later, mysterious problems surfaced in the bill-payment application module for which the Charge had originally been written. Strange Charges appeared that no one remembered entering and that didn’t make any sense. The program began to crash when some functions were used, particularly the month-to-date tax report. Investigation revealed that the crash resulted when a function was used that summed up the amount deductible for all the current month’s payments. The mystery records had no value in the “percent deductible” field, although the validation of the data-entry application required it and even put in a default value.
问题在于,这两个团体拥有不同的模型,但他们没有意识到这一点,而且也没有流程来检测每个人都对收费的性质做出了假设,这些假设在他们自己的环境中是有用的(向客户收费还是向供应商付费)。当他们的代码组合在一起而没有解决这些矛盾时,结果就是不可靠的软件。
The problem was that these two groups had different models, but they did not realize it, and there were no processes in place to detect it. Each made assumptions about the nature of a charge that were useful in their context (billing customers versus paying vendors). When their code was combined without resolving these contradictions, the result was unreliable software.
如果他们能更清楚地认识到这一现实,他们就可以有意识地决定如何处理它。这可能意味着共同努力制定一个通用模型,然后编写一个自动化测试套件,以防止将来出现意外。或者它可能只是意味着达成协议开发单独的模型,不干涉彼此的代码。无论哪种方式,它都始于对每个模型适用范围的明确协议。
If only they had been more aware of this reality, they could have consciously decided how to deal with it. That might have meant working together to hammer out a common model and then writing an automated test suite to prevent future surprises. Or it might simply have meant an agreement to develop separate models and keep hands off each other’s code. Either way, it starts with an explicit agreement on the boundaries within which each model applies.
当他们意识到问题所在后,他们做了什么?他们创建了单独的客户费用和供应商费用类,并根据相应团队的需求分别进行了定义。眼前的问题解决了,他们又回到了之前的做法。算了。
What did they do once they knew about the problem? They created separate Customer Charge and Supplier Charge classes and defined each according to the needs of the corresponding team. The immediate problem having been solved, they went back to doing things just as before. Oh well.
尽管我们很少明确地考虑这一点,但模型的最基本要求是它必须具有内部一致性;其术语始终具有相同的含义,并且不包含任何矛盾的规则。模型的内部一致性,即每个术语都没有歧义并且没有规则相互矛盾,称为统一性。除非模型在逻辑上一致,否则它毫无意义。在理想世界中,我们将拥有一个涵盖整个企业领域的单一模型。该模型将是统一的,没有任何矛盾或重叠的术语定义。关于该领域的每个逻辑陈述都将是一致的。
Although we seldom think about it explicitly, the most fundamental requirement of a model is that it be internally consistent; that its terms always have the same meaning, and that it contain no contradictory rules. The internal consistency of a model, such that each term is unambiguous and no rules contradict, is called unification. A model is meaningless unless it is logically consistent. In an ideal world, we would have a single model spanning the whole domain of the enterprise. This model would be unified, without any contradictory or overlapping definitions of terms. Every logical statement about the domain would be consistent.
但大型系统开发的世界并不是理想的世界。在整个企业系统中保持这种统一水平的麻烦比它的价值要多。允许多种模型在系统的不同部分开发是必要的,但我们需要谨慎选择系统的哪些部分可以分离以及它们之间的关系。我们需要一些方法来保持模型的关键部分紧密统一。所有这些都不会自动发生,也不会出于良好的意图。只有通过有意识的设计决策和特定流程的制定才能实现。大型系统的领域模型的完全统一既不可行,也不经济高效。
But the world of large systems development is not the ideal world. To maintain that level of unification in an entire enterprise system is more trouble than it is worth. It is necessary to allow multiple models to develop in different parts of the system, but we need to make careful choices about which parts of the system will be allowed to diverge and what their relationship to each other will be. We need ways of keeping crucial parts of the model tightly unified. None of this happens by itself or through good intentions. It happens only through conscious design decisions and institution of specific processes. Total unification of the domain model for a large system will not be feasible or cost-effective.
有时人们会反对这个事实。大多数人认为,多个模型的代价是限制集成并使沟通变得繁琐。除此之外,拥有多个模型似乎有点不雅。这种对多个模型的抵制有时会导致非常雄心勃勃的尝试,将大型项目中的所有软件统一到一个模型下。我知道我犯过这种过度行为。但请考虑一下风险。
Sometimes people fight this fact. Most people see the price that multiple models exact by limiting integration and making communication cumbersome. On top of that, having more than one model somehow seems inelegant. This resistance to multiple models sometimes leads to very ambitious attempts to unify all the software in a large project under a single model. I know I’ve been guilty of this kind of overreaching. But consider the risks.
1.可能同时尝试进行太多遗留替换。
1. Too many legacy replacements may be attempted at once.
2.大型项目可能因协调开销超出其能力而陷入停滞。
2. Large projects may bog down because the coordination overhead exceeds their abilities.
3.具有特殊要求的应用程序可能不得不使用不能完全满足其需求的模型,从而迫使它们将行为置于其他地方。
3. Applications with specialized requirements may have to use models that don’t fully satisfy their needs, forcing them to put behavior elsewhere.
4.相反,试图用单一模型满足所有人可能会导致复杂的选项,使模型难以使用。
4. Conversely, attempting to satisfy everyone with a single model may lead to complex options that make the model difficult to use.
此外,模型分歧可能来自政治分歧和不同的管理重点,也可能来自技术关注。不同模型的出现可能是团队组织和开发过程的结果。因此,即使没有技术因素阻碍全面整合,项目仍可能面临多种模型。
What’s more, model divergences are as likely to come from political fragmentation and differing management priorities as from technical concerns. And the emergence of different models can be a result of team organization and development process. So even when no technical factor prevents full integration, the project may still face multiple models.
鉴于不可能为整个企业维持统一的模型,我们不必听天由命。通过积极主动地决定哪些应该统一,并务实地认识到哪些不统一,我们可以创建一个清晰、共享的状况图景。有了这些,我们就可以着手确保我们想要统一的部分保持原样,而未统一的部分不会引起混乱或腐败。
Given that it isn’t feasible to maintain a unified model for an entire enterprise, we don’t have to leave ourselves at the mercy of events. Through a combination of proactive decisions about what should be unified and pragmatic recognition of what is not unified, we can create a clear, shared picture of the situation. With that in hand, we can set about making sure that the parts we want to unify stay that way, and the parts that are not unified don’t cause confusion or corruption.
我们需要一种方法来标记不同模型之间的界限和关系。我们需要有意识地选择我们的策略,然后始终如一地遵循我们的策略。
We need a way to mark the boundaries and relationships between different models. We need to choose our strategy consciously and then follow our strategy consistently.
本章列出了识别、传达和选择模型的限制及其与其他。一切都从绘制项目当前的地形图开始。有界上下文定义了每个模型的适用范围,而上下文图则提供了项目上下文及其之间关系的全局概述。这种歧义的减少本身将改变项目发生的方式,但这并不一定足够。一旦我们有了有界上下文,持续集成的过程将使模型保持统一。
This chapter lays out techniques for recognizing, communicating, and choosing the limits of a model and its relationships to others. It all starts with mapping the current terrain of the project. A BOUNDED CONTEXT defines the range of applicability of each model, while a CONTEXT MAP gives a global overview of the project’s contexts and the relationships between them. This reduction of ambiguity will, in and of itself, change the way things happen on the project, but it isn’t necessarily enough. Once we have a CONTEXT BOUNDED, a process of CONTINUOUS INTEGRATION will keep the model unified.
然后,从这种稳定的情况出发,我们可以开始转向更有效的策略来确定上下文界限并将它们关联起来,范围从具有共享内核的紧密关联的上下文到各行其道的松散耦合模型。
Then, starting from this stable situation, we can start to migrate toward more effective strategies for BOUNDING CONTEXTS and relating them, ranging from closely allied contexts with SHARED KERNELS to loosely coupled models that go their SEPARATE WAYS.
图 14.1. 模型完整性模式的导航图
Figure 14.1. A navigation map for model integrity patterns
细胞之所以能够存在,是因为细胞膜决定了什么是进,什么是出,以及什么可以通过。
Cells can exist because their membranes define what is in and out and determine what can pass.
大型项目会共存多种模型,这在很多情况下都很有效。不同的模型适用于不同的环境。例如,你可能必须将新软件与你的团队无法控制的外部系统集成。这种情况对每个人来说可能都很清楚,因为它是一种不适用正在开发的模型的独特环境,但其他情况可能更加模糊和令人困惑。在本章开头的故事中,两个团队正在为同一个新系统开发不同的功能。他们是在同一个模型上工作吗?他们的意图是共享至少部分工作,但没有分界线告诉他们共享了什么或没有共享什么。而且,他们没有制定流程来将共享的模型结合在一起或快速检测分歧。只有在系统行为突然变得不可预测后,他们才意识到他们已经出现分歧。
Multiple models coexist on big projects, and this works fine in many cases. Different models apply in different contexts. For example, you may have to integrate your new software with an external system over which your team has no control. A situation like this is probably clear to everyone as a distinct context where the model under development doesn’t apply, but other situations can be more vague and confusing. In the story that opened this chapter, two teams were working on different functionality for the same new system. Were they working on the same model? Their intention was to share at least part of what they did, but there was no demarcation to tell them what they did or did not share. And they had no process in place to hold a shared model together or quickly detect divergences. They realized they had diverged only after their system’s behavior suddenly became unpredictable.
即使是单个团队也可能拥有多个模型。沟通可能会中断,导致对模型的解释产生微妙的冲突。旧代码通常反映的是模型的早期概念,与当前模型略有不同。
Even a single team can end up with multiple models. Communication can lapse, leading to subtly conflicting interpretations of the model. Older code often reflects an earlier conception of the model that is subtly different from the current model.
每个人都知道另一个系统的数据格式不同,需要进行数据转换,但这只是问题的机械方面。更根本的是两个系统中隐含的模型。当差异不是来自外部系统,而是来自同一个代码库时,就更不可能被识别。然而,这种情况在所有大型团队项目中都会发生。
Everyone is aware that the data format of another system is different and calls for a data conversion, but this is only the mechanical dimension of the problem. More fundamental is the difference in the models implicit in the two systems. When the discrepancy is not with an external system, but within the same code base, it is even less likely to be recognized. Yet this happens on all large team projects.
任何大型项目都会使用多种模型。然而,当基于不同模型的代码组合在一起时,软件就会变得漏洞百出、不可靠且难以理解。团队成员之间的沟通变得混乱。通常不清楚在什么情况下不应该 应用 某个模型。
Multiple models are in play on any large project. Yet when code based on distinct models is combined, software becomes buggy, unreliable, and difficult to understand. Communication among team members becomes confused. It is often unclear in what context a model should not be applied.
当运行的代码无法正常工作时,问题最终会暴露出来,但问题始于团队的组织方式和人们的互动方式。因此,要明确模型的背景,我们必须同时查看项目及其最终产品(代码、数据库模式等)。
Failure to keep things straight is ultimately revealed when the running code doesn’t work right, but the problem starts in the way teams are organized and the way people interact. Therefore, to clarify the context of a model, we have to look at both the project and its end products (code, database schemas, and so on).
模型适用于特定上下文。上下文可能是代码的某个部分,也可能是某个团队的工作。对于在头脑风暴会议中发明的模型,上下文可能仅限于该特定对话。本书示例中使用的模型的上下文是该特定示例部分及其后续讨论。模型上下文是必须应用的一组条件,以便能够说模型中的术语具有特定含义。
A model applies in a context. The context may be a certain part of the code, or the work of a particular team. For a model invented in a brainstorming session, the context could be limited to that particular conversation. The context of a model used in an example in this book is that particular example section and any later discussion of it. The model context is whatever set of conditions must apply in order to be able to say that the terms in a model have a specific meaning.
要开始解决多模型问题,我们需要明确定义特定模型的范围,将其作为软件系统的一个有界部分,在此范围内将应用单个模型,并尽可能保持统一。此定义必须与团队组织协调一致。
To begin to solve the problems of multiple models, we need to define explicitly the scope of a particular model as a bounded part of a software system within which a single model will apply and will be kept as unified as possible. This definition has to be reconciled with the team organization.
所以:
Therefore:
明确定义模型适用的上下文。明确设定团队组织、应用程序特定部分内的使用以及代码库和数据库模式等物理表现方面的界限。在这些界限内严格保持模型的一致性,但不要被外部问题分散注意力或困惑。
Explicitly define the context within which a model applies. Explicitly set boundaries in terms of team organization, usage within specific parts of the application, and physical manifestations such as code bases and database schemas. Keep the model strictly consistent within these bounds, but don’t be distracted or confused by issues outside.
有界上下文界定了特定模型的适用性,以便团队成员对必须保持一致的内容以及它与其他上下文的关系有清晰且一致的理解。在该上下文内,努力保持模型在逻辑上的统一,但不必担心超出这些界限的适用性。在其他上下文中,其他模型适用,但术语、概念和规则以及通用语言的方言有所不同。通过划定明确的边界,您可以保持模型的纯粹性,从而在适用的情况下保持其有效性。同时,在将注意力转移到其他CONTEXTS时,您可以避免混淆。跨越边界的集成必然会涉及一些转换,您可以明确地进行分析。
A BOUNDED CONTEXT delimits the applicability of a particular model so that team members have a clear and shared understanding of what has to be consistent and how it relates to other CONTEXTS. Within that CONTEXT, work to keep the model logically unified, but do not worry about applicability outside those bounds. In other CONTEXTS, other models apply, with differences in terminology, in concepts and rules, and in dialects of the UBIQUITOUS LANGUAGE. By drawing an explicit boundary, you can keep the model pure, and therefore potent, where it is applicable. At the same time, you avoid confusion when shifting your attention to other CONTEXTS. Integration across the boundaries necessarily will involve some translation, which you can analyze explicitly.
一家航运公司有一个内部项目,开发一款用于预订货物的新应用程序。此应用程序将由对象模型驱动。此模型适用的BOUNDED CONTEXT是什么?要回答这个问题,我们必须看看项目正在发生什么。请记住,这是对项目的现状的观察,而不是对项目理想状态的观察。
A shipping company has an internal project to develop a new application for booking cargo. This application is to be driven by an object model. What is the BOUNDED CONTEXT within which this model applies? To answer this question, we have to look at what is happening on the project. Keep in mind, this is a look at the project as it is, not as it ideally should be.
一个项目团队正在开发预订应用程序本身。他们不需要修改模型对象,但他们正在构建的应用程序必须显示和操作这些对象。这个团队是模型的消费者。该模型在应用程序(其主要消费者)内有效,因此预订应用程序符合规定。
One project team is working on the booking application itself. They are not expected to modify the model objects, but the application they are building has to display and manipulate those objects. This team is a consumer of the model. The model is valid within the application (its primary consumer), and therefore the booking application is in bounds.
已完成的预订必须传递给传统的货物跟踪系统。事先已决定新模型将不同于传统模型,因此传统的货物跟踪系统超出了边界。新模型和传统模型之间的必要转换是传统维护团队的责任。转换机制不是由模型驱动的。它不在有界上下文中。(它是边界本身的一部分,将在上下文图中讨论。)转换脱离上下文(不基于模型)是件好事。要求传统团队真正使用模型是不现实的,因为他们的主要工作不在上下文中。
The completed bookings have to be passed to the legacy cargotracking system. A decision was made up front that the new model would depart from that of the legacy, so the legacy cargotracking system is outside the boundary. Necessary translation between the new model and the legacy is to be the responsibility of the legacy maintenance team. The translation mechanism is not driven by the model. It is not in the BOUNDED CONTEXT. (It is part of the boundary itself, which will be discussed in CONTEXT MAP.) It is good that translation is out of CONTEXT (not based on the model). It would be unrealistic to ask the legacy team to make any real use of the model because their primary work is out of CONTEXT.
负责模型的团队处理每个对象的整个生命周期,包括持久性。由于该团队控制着数据库架构,因此他们有意保持对象关系映射的简单性。换句话说,架构由模型驱动,因此在界限之内。
The team responsible for the model deals with the whole life cycle of each object, including persistence. Because this team has control of the database schema, they’ve been deliberately keeping the object-relational mapping straightforward. In other words, the schema is being driven by the model and therefore is in bounds.
还有一个团队正在开发一个用于安排货船航行的模型和应用程序。调度和预订团队是一起发起的,两个团队都打算开发一个统一的系统。这两个团队偶尔会相互协调,偶尔也会共享对象,但并没有系统地进行。他们不在同一个有界情境中工作。这是一种风险,因为他们不认为自己是在单独的模型上工作。在他们整合的范围内,除非他们建立流程来管理这种情况,否则就会出现问题。(本章后面讨论的共享内核可能是个不错的选择。)但第一步是认清现状。他们不在同一个情境中,应该停止尝试共享代码,直到做出一些更改。
Yet another team is working on a model and application for scheduling the voyages of the cargo ships. The scheduling and booking teams were initiated together, and both teams had intended to produce a single, unified system. The two teams have casually coordinated with each other, and they occasionally share objects, but they are not systematic about it. They are not working in the same BOUNDED CONTEXT. This is a risk, because they do not think of themselves as working on separate models. To the extent they integrate, there will be problems unless they put in place processes to manage the situation. (The SHARED KERNEL, discussed later in this chapter, might be a good choice.) The first step, though, is to recognize the situation as it is. They are not in the same CONTEXT and should stop trying to share code until some changes are made.
此BOUNDED CONTEXT由此特定模型驱动的系统的所有方面组成:模型对象、保存模型对象的数据库模式以及预订应用程序。两个团队主要在此CONTEXT中工作:建模团队和应用程序团队。信息必须与旧式跟踪系统交换,而旧式团队主要负责此边界的转换,建模团队则负责配合。预订模型和航次计划模型之间没有明确定义的关系,定义这种关系应该是这些团队的首要行动之一。与此同时,他们应该非常小心地共享代码或数据。
This BOUNDED CONTEXT is made up of all those aspects of the system that are driven by this particular model: the model objects, the database schema that persists the model objects, and the booking application. Two teams work primarily in this CONTEXT: the modeling team and the application team. Information has to be exchanged with the legacy tracking system, and the legacy team has primary responsibility for the translation at this boundary, with cooperation from the modeling team. There is no clearly defined relationship between the booking model and the voyage schedule model, and defining that relationship should be one of those teams’ first actions. In the meantime, they should be very careful about sharing code or data.
那么,定义这个有界上下文有什么好处呢?对于在上下文中工作的团队来说:清晰度。这两个团队知道他们必须与一个模型保持一致。他们根据这些知识做出设计决策并注意是否存在断裂。对于外部的团队来说:自由。他们不必走在灰色地带,不使用相同的模型,但不知何故觉得他们应该这样做。但在这个特定案例中最具体的收获可能是意识到预订模型团队和航程计划团队之间非正式共享的风险。为了避免问题,他们确实需要决定共享的成本/收益权衡,并制定流程使其发挥作用。除非每个人都了解模型上下文的界限在哪里,否则这不会发生。
So, what has been gained by defining this BOUNDED CONTEXT? For the teams working in CONTEXT: clarity. Those two teams know they must stay consistent with one model. They make design decisions in that knowledge and watch for fractures. For the teams outside: freedom. They don’t have to walk in the gray zone, not using the same model, yet somehow feeling they should. But the most concrete gain in this particular case is probably realizing the risk of the informal sharing between the booking model team and the voyage schedule team. To avoid problems, they really need to decide on the cost/benefit trade-offs of sharing and put in processes to make it work. This won’t happen unless everyone understands where the bounds of the model contexts are.
当然,边界是特殊的地方。有界上下文与其邻居之间的关系需要小心和注意。上下文图绘制了领土,给出了上下文及其连接的全景图,而几种模式定义了上下文之间各种关系的性质。连续集成过程保持了有界上下文内模型的统一性。
Of course, boundaries are special places. The relationships between a BOUNDED CONTEXT and its neighbors require care and attention. The CONTEXT MAP charts the territory, giving the big picture of the CONTEXTS and their connections, while several patterns define the nature of the various relationships between CONTEXTS. And a process of CONTINUOUS INTEGRATION preserves unity of the model within a BOUNDED CONTEXT.
但在继续讨论之前,当一个模型的统一性被打破时,它会是什么样子?你如何识别概念上的分裂?
But before proceeding to all that, what does it look like when unification of a model is breaking down? How do you recognize conceptual splinters?
许多症状可能表明模型存在未被识别的差异。最明显的症状是编码接口不匹配。更微妙的是,意外行为可能是一个迹象。持续集成过程与自动化测试可以帮助发现这类问题。但早期预警通常是语言混乱。
Many symptoms may indicate unrecognized model differences. Some of the most obvious are when coded interfaces don’t match up. More subtly, unexpected behavior is a likely sign. The CONTINUOUS INTEGRATION process with automated tests can help catch these kinds of problems. But the early warning is usually a confusion of language.
将不同模型的元素组合会导致两类问题:重复概念和错误同源词。概念重复意味着有两个模型元素(及相应的实现)实际上代表了相同的概念。每当这些信息发生变化时,都必须在两个地方进行转换更新。每当新知识导致其中一个对象发生变化时,另一个对象也必须重新分析和更改。只不过重新分析实际上并不会发生,所以结果是同一概念的两个版本遵循不同的规则,甚至有不同的数据。最重要的是,团队成员必须学习做同一件事的两种方法,以及同步它们的所有方法。
Combining elements of distinct models causes two categories of problems: duplicate concepts and false cognates. Duplication of concepts means that there are two model elements (and attendant implementations) that actually represent the same concept. Every time this information changes, it has to be updated in two places with conversions. Every time new knowledge leads to a change in one of the objects, the other has to be reanalyzed and changed too. Except the reanalysis doesn’t happen in reality, so the result is two versions of the same concept that follow different rules and even have different data. On top of that, the team members must learn not one but two ways of doing the same thing, along with all the ways they are being synchronized.
假同源词可能不太常见,但危害更大。这种情况是,当两个使用相同术语(或实现对象)的人认为他们在谈论同一件事,但实际上并非如此时。本章开头的示例(两个不同的业务活动都称为Charge)很典型,但当两个定义实际上与领域中的同一方面相关,但概念化方式略有不同时,冲突可能会更加微妙。假同源词会导致开发团队互相踩踏对方的代码、数据库团队内部存在奇怪的矛盾和沟通混乱。假同源词这个术语通常适用于自然语言。例如,学习西班牙语的英语人士经常误用embarazada这个词。这个词的意思不是“尴尬”,而是“怀孕”。糟糕。
False cognates may be slightly less common, but more insidiously harmful. This is the case when two people who are using the same term (or implemented object) think they are talking about the same thing, but really are not. The example in the beginning of this chapter (two different business activities both called Charge) is typical, but conflicts can be even subtler when the two definitions are actually related to the same aspect in the domain, but have been conceptualized in slightly different ways. False cognates lead to development teams that step on each other’s code, databases that have weird contradictions, and confusion in communication within the team. The term false cognate is ordinarily applied to natural languages. For example, English speakers learning Spanish often misuse the word embarazada. This word does not mean “embarrassed”; it means “pregnant.” Oops.
当你发现这些问题时,你的团队必须做出决定。你可能希望将模型重新整合起来并优化流程以防止碎片化。或者碎片化可能是由于一些团体出于好的理由想要将模型拉向不同的方向,而你可能决定让他们独立发展。处理这些问题是本章剩余模式的主题。
When you detect these problems, your team will have to make a decision. You may want to pull the model back together and refine the processes to prevent fragmentation. Or the fragmentation may be a result of groups who want to pull the model in different directions for good reasons, and you may decide to let them develop independently. Dealing with these issues is the subject of the remaining patterns in this chapter.
Having defined a BOUNDED CONTEXT, we must keep it sound.
当许多人在同一个有界上下文中工作时,模型很容易出现分裂。团队越大,问题就越大,但只要三四个人就可能遇到严重问题。然而,将系统分解成越来越小的上下文最终会失去宝贵的集成度和一致性。
When a number of people are working in the same BOUNDED CONTEXT, there is a strong tendency for the model to fragment. The bigger the team, the bigger the problem, but as few as three or four people can encounter serious problems. Yet breaking down the system into ever-smaller CONTEXTS eventually loses a valuable level of integration and coherency.
有时,开发人员并不完全理解其他人建模的某些对象或交互的意图,他们会对其进行更改,使其无法用于其原始目的。有时,他们没有意识到他们正在研究的概念已经体现在模型的另一部分中,因此他们会(不准确地)复制这些概念和行为。有时,他们知道这些其他表达方式,但害怕篡改它们,因为担心会破坏现有的功能,因此他们会继续复制概念和功能。
Sometimes developers do not fully understand the intent of some object or interaction modeled by someone else, and they change it in a way that makes it unusable for its original purpose. Sometimes they don’t realize that the concepts they are working on are already embodied in another part of the model and they duplicate (inexactly) those concepts and behavior. Sometimes they are aware of those other expressions but are afraid to tamper with them, for fear of corrupting the existing functionality, and so they proceed to duplicate concepts and functionality.
保持开发统一系统所需的沟通水平非常困难,无论该系统规模大小。我们需要增加沟通并降低复杂性的方法。我们还需要安全网来防止过度谨慎的行为,例如开发人员因为担心会破坏现有代码而重复功能。
It is very hard to maintain the level of communication needed to develop a unified system of any size. We need ways of increasing communication and reducing complexity. We also need safety nets that prevent overcautious behavior, such as developers duplicating functionality because they are afraid they will break existing code.
正是在这种环境下,极限编程 (XP) 才真正发挥了其作用。许多 XP 实践都针对这一特定问题,即维护一个不断被许多人更改的连贯设计。最纯粹的 XP 非常适合在单个BOUNDED CONTEXT中维护模型完整性。但是,无论是否使用 XP,都必须进行某种持续集成过程。
It is in this environment that Extreme Programming (XP) really comes into its own. Many XP practices are aimed at this specific problem of maintaining a coherent design that is being constantly changed by many people. XP in its purest form is a nice fit for maintaining model integrity within a single BOUNDED CONTEXT. However, whether or not XP is being used, it is essential to have some process of CONTINUOUS INTEGRATION.
持续集成意味着上下文中的所有工作都以足够频繁的频率进行合并和保持一致,以便当出现分歧时,可以快速发现并纠正。持续集成与领域驱动设计中的其他所有内容一样,在两个层面上运作:(1) 模型概念的集成和 (2) 实现的集成。
CONTINUOUS INTEGRATION means that all work within the context is being merged and made consistent frequently enough that when splinters happen they are caught and corrected quickly. CONTINUOUS INTEGRATION, like everything else in domain-driven design, operates at two levels: (1) the integration of model concepts and (2) the integration of the implementation.
概念通过团队成员之间的不断沟通得以整合。团队必须培养对不断变化的模型的共同理解。许多实践都有帮助,但最根本的是不断敲定通用语言。同时,实施工件通过系统的合并/构建/测试过程进行集成,该过程可尽早暴露模型碎片。集成过程有很多种,但大多数有效的集成过程都具有以下特点:
Concepts are integrated by constant communication among team members. The team must cultivate a shared understanding of the ever-changing model. Many practices help, but the most fundamental is constantly hammering out the UBIQUITOUS LANGUAGE. Meanwhile, the implementation artifacts are being integrated by a systematic merge/build/test process that exposes model splinters early. Many processes for integration are used, but most of the effective ones share these characteristics:
• 逐步、可重复的合并/构建技术;
• A step-by-step, reproducible merge/build technique;
• 自动化测试套件;以及
• Automated test suites; and
• 对未集成更改的生命周期设置一些合理的小上限的规则。
• Rules that set some reasonably small upper limit on the lifetime of unintegrated changes.
有效流程的另一面是概念整合,尽管它很少被正式纳入。
The other side of the coin in effective processes, although it is seldom formally included, is conceptual integration.
•在模型和应用的讨论中不断练习通用语言
• Constant exercise of the UBIQUITOUS LANGUAGE in discussions of the model and application
大多数敏捷项目至少每天都会合并每个开发人员的代码更改。频率可以根据变更的速度进行调整,只要在其他团队成员完成大量不兼容的工作之前合并任何未集成的变更即可。
Most Agile projects have at least daily merges of each developer’s code changes. The frequency can be adjusted to the pace of change, as long as any unintegrated change would be merged before a significant amount of incompatible work could be done by other team members.
在模型驱动设计中,概念的集成为实施的集成铺平了道路,而实施的集成则证明了模型的有效性和一致性,并暴露出其中的碎片。
In a MODEL-DRIVEN DESIGN, the integration of concepts smooths the way for the integration of the implementation, while the integration of the implementation proves the validity and consistency of the model and exposes splinters.
所以:
Therefore:
建立频繁合并所有代码和其他实施工件的流程,并通过自动化测试快速标记碎片。随着概念在不同人的头脑中演变,不断练习通用语言以形成模型的共享视图。
Institute a process of merging all code and other implementation artifacts frequently, with automated tests to flag fragmentation quickly. Relentlessly exercise the UBIQUITOUS LANGUAGE to hammer out a shared view of the model as the concepts evolve in different people’s heads.
最后,不要让工作变得比它应有的更大。持续集成只有在有界上下文中才是必要的。涉及相邻上下文的设计问题(包括翻译)不必以相同的速度处理。
Finally, do not make the job any bigger than it has to be. CONTINUOUS INTEGRATION is essential only within a BOUNDED CONTEXT. Design issues involving neighboring CONTEXTS, including translation, don’t have to be dealt with at the same pace.
连续集成将应用于任何大于两人任务的单个有界上下文中。它保持了单个模型的完整性。当多个有界上下文共存时,您必须决定它们的关系并设计任何必要的接口。...
CONTINUOUS INTEGRATION would be applied within any individual BOUNDED CONTEXT that is larger than a two-person task. It maintains the integrity of that single model. When multiple BOUNDED CONTEXTS coexist, you have to decide on their relationships and design any necessary interfaces. . . .
单独的有界上下文仍然没有提供全局视图。其他模型的背景可能仍然模糊且不断变化。
An individual BOUNDED CONTEXT still does not provide a global view. The context of other models may still be vague and in flux.
其他团队的人不太清楚上下文的界限,会在不知不觉中做出模糊边界或使互连复杂化的改变。当必须在不同的上下文之间建立连接时,它们往往会相互渗透。
People on other teams won’t be very aware of the CONTEXT bounds and will unknowingly make changes that blur the edges or complicate the interconnections. When connections must be made between different contexts, they tend to bleed into each other.
BOUNDED CONTEXTS之间的代码重用是需要避免的危险。功能和数据的集成必须经过转换。您可以通过定义不同上下文之间的关系并创建项目上所有模型上下文的全局视图来减少混乱。
Code reuse between BOUNDED CONTEXTS is a hazard to be avoided. Integration of functionality and data must go through a translation. You can reduce confusion by defining the relationship between the different contexts and creating a global view of all the model contexts on the project.
上下文图位于项目管理和软件设计之间的重叠部分。自然的事件发展过程是边界遵循团队组织的轮廓。密切合作的人自然会共享一个模型上下文。不同团队的人,或者那些不交流的人,即使他们在同一个团队,也会分成不同的上下文。物理办公空间也会产生影响,因为位于建筑物两端的团队成员——更不用说不同的城市——可能会在没有额外整合努力的情况下分道扬镳。大多数项目经理直观地认识到这些因素,并围绕子系统广泛地组织团队。但团队组织与软件模型和设计之间的相互关系仍然不够突出。经理和团队成员需要清晰地了解软件模型和设计正在进行的概念细分。
A CONTEXT MAP is in the overlap between project management and software design. The natural course of events is for the boundaries to follow the contours of team organization. People who work closely will naturally share a model context. People on different teams, or those that don’t talk, even if they are on the same team, will split off into different contexts. Physical office space can have an impact too, as team members on opposite ends of a building—not to mention different cities—will probably diverge without extra integration effort. Most project managers intuitively recognize these factors and broadly organize teams around subsystems. But the interrelationship between team organization and software model and design is still not prominent enough. Both managers and team members need a clear view into the ongoing conceptual subdivision of the software model and design.
所以:
Therefore:
确定项目中使用的每个模型并定义其BOUNDED CONTEXT。这包括非面向对象子系统的隐式模型。命名每个BOUNDED CONTEXT,并将名称作为UBIQUITOUS LANGUAGE的一部分。
Identify each model in play on the project and define its BOUNDED CONTEXT. This includes the implicit models of non-object-oriented subsystems. Name each BOUNDED CONTEXT, and make the names part of the UBIQUITOUS LANGUAGE.
描述模型之间的接触点,概述任何通信的明确翻译并强调任何共享。
Describe the points of contact between the models, outlining explicit translation for any communication and highlighting any sharing.
绘制 现有 地形图。稍后再进行改造。
Map the existing terrain. Take up transformations later.
在每个有界上下文中,您将拥有统一语言的连贯方言。有界上下文的名称本身将进入该语言,这样您就可以通过清晰地表达设计任何部分的模型。
Within each BOUNDED CONTEXT, you will have a coherent dialect of the UBIQUITOUS LANGUAGE. The names of the BOUNDED CONTEXTS will themselves enter that LANGUAGE so that you can speak unambiguously about the model of any part of the design by making your CONTEXT clear.
地图不必以任何特定形式记录。我发现本章中的图表有助于可视化和传达地图。其他人可能更喜欢文本描述或不同的图形表示。在某些情况下,团队成员之间的讨论可能就足够了。详细程度可以根据需要而变化。无论地图采用何种形式,项目中的每个人都必须共享和理解它。它必须为每个BOUNDED CONTEXT提供清晰的名称,并且必须明确接触点及其性质。
The MAP does not have to be documented in any particular form. I find diagrams like the ones in this chapter to be helpful in visualizing and communicating the map. Others may prefer a more textual description or a different graphical representation. In some situations, discussion among teammates may be sufficient. The level of detail can vary according to need. Whatever form the MAP takes, it must be shared and understood by everyone on the project. It must provide a clear name for each BOUNDED CONTEXT, and it must make the points of contact and their natures clear.
有界上下文之间的关系有多种形式,这取决于设计问题和项目组织问题。本章稍后将列出在不同情况下有效的上下文之间的关系的各种模式,这些模式可以提供术语来描述您在自己的MAP中找到的关系。请记住,上下文 MAP始终代表实际情况,您找到的关系最初可能并不适合这些模式。如果它们很接近,您可能希望使用模式名称,但不要强迫它。只需描述您找到的关系。稍后您可以开始迁移到更标准化的关系。
The relationships between BOUNDED CONTEXTS take many forms depending on both design issues and project organizational issues. Later, this chapter will lay out various patterns of relationships between CONTEXTS that are effective in different situations, and that can provide terms to describe the relationships you find in your own MAP. Keeping in mind that the CONTEXT MAP always represents the situation as it stands, the relationships you find may not fit these patterns initially. If they fall close, you may wish to use the pattern name, but don’t force it. Just describe the relationships you find. Later you can begin to migrate toward more standardized relationships.
那么,如果你发现了一个碎片——一个完全纠缠在一起但又包含不一致之处的模型,你会怎么做?在地图上放一条龙,描述完一切。然后,以准确的全球视野,解决混淆点。一个小碎片可以修复,并可以建立流程来加固它。如果关系模糊,你可以选择最近的模式并朝着它前进。你的首要任务是得到一个清晰的上下文图,这可能意味着解决你发现的实际问题。但不要让这种必要的修复导致大规模重组。在你有一个明确的上下文图,把你所有的工作放进一些有界上下文,所有连接的模型之间有明确的关系之前,只改变彻底的矛盾。
So, what do you do if you’ve discovered a splinter—a model that is completely entangled but contains inconsistencies? Put a dragon on the map and finish describing everything. Then, with an accurate global view, address the points of confusion. A minor splinter can be repaired, and processes can be put in place to shore it up. If a relationship is vague, you can choose the nearest pattern and move toward it. Your first order of business is to arrive at a clear CONTEXT MAP, and this may mean fixing real problems you have found. But don’t let this necessary repair lead to wholesale reorganization. Until you have an unambiguous CONTEXT MAP that places all your work into some BOUNDED CONTEXT, with explicit relationships between all connected models, change only the outright contradictions.
一旦你有了连贯的上下文图,你就会发现你想改变的东西。你可以对团队的组织或设计进行深思熟虑的改变。记住,在现实改变完成之前不要改变地图。
Once you have a coherent CONTEXT MAP, you’ll see things you want to change. You can make considered changes to the organization of teams or to the design. Remember, don’t change the map until the change in reality is done.
我们再次回到运输系统。该应用程序的主要功能之一是在预订时自动安排货物路线。模型如下:
We return again to the shipping system. One of the application’s major features was to be the automatic routing of cargos at booking time. The model was something like this:
图 14.2
Figure 14.2
路由服务是一种服务 它封装了由无副作用函数组成的意图揭示接口背后的机制。这些函数的结果以断言为特征。
The Routing Service is a SERVICE that encapsulates a mechanism behind an INTENTION-REVEALING INTERFACE made up of SIDEEFFECT-FREE FUNCTIONS. The results of those functions are characterized with ASSERTIONS.
1.接口声明,当传入RouteSpecification时,会返回一个Itinerary 。
1. The interface declares that when a Route Specification is passed in, an Itinerary will be returned.
2. ASSERTION 声明返回的行程将满足传入的路线规范。
2. The ASSERTION states that the returned Itinerary will satisfy the Route Specification that was passed in.
没有人说明这项非常困难的任务是如何完成的。现在让我们去幕后看看其中的机制。
Nothing is stated about how this very difficult task is performed. Now let’s go behind the curtain to see the mechanism.
最初,在本示例所基于的项目中,我对“路线服务”的内部结构过于教条。我希望实际的路线操作能够通过一个扩展的领域模型来完成,该模型将代表船舶航程并将其直接与“行程”中的航段联系起来。但负责路线问题的团队指出,为了使其性能良好并利用成熟的算法,解决方案需要以优化网络的形式实现,航程的每一段都表示为矩阵中的一个元素。他们坚持为此采用独特的航运运营模型。
Initially on the project on which this example is based, I was too dogmatic about the internals of the Routing Service. I wanted the actual routing operation to be done with an extended domain model that would represent vessel voyages and directly relate them to the Legs in the Itinerary. But the team working on the routing problem pointed out that, to make it perform well and to draw on well-established algorithms, the solution needed to be implemented as an optimized network, with each leg of a voyage represented as an element in a matrix. They insisted on a distinct model of shipping operations for this purpose.
他们显然对当时设计的路线规划过程的计算需求是正确的,因此,由于没有更好的想法,我屈服了。实际上,我们创建了两个独立的有界上下文,每个上下文都有自己的运输操作概念组织。(见图14.3。)
They were clearly right about the computational demands of the routing process as then designed, and so, lacking any better idea, I yielded. In effect, we created two separate BOUNDED CONTEXTS, each of which had its own conceptual organization of shipping operations. (See Figure 14.3.)
图 14.3。形成两个有界上下文,以便应用高效的路由算法
Figure 14.3. Two BOUNDED CONTEXTS formed to allow efficient routing algorithms to be applied
我们的要求是接收路由服务请求,将其转换为网络遍历服务可以理解的术语,然后获取结果并将其转换为路由服务预期提供的形式。
Our requirement was to take a Routing Service request, translate it into terms the Network Traversal Service could understand, then take the result and translate it into the form a Routing Service is expected to give.
这意味着没有必要映射这两个模型中的所有内容,而只需进行两种特定的转换:
This means it was not necessary to map everything in these two models, but only to be able to make two specific translations:
路线指定→位置代码列表
Route Specification → List of location codes
节点ID列表→行程
List of Node IDs → Itinerary
为了做到这一点,我们必须了解一个模型中元素的含义,并弄清楚如何用另一个模型来表达它。
To do this, we have to look at the meaning of an element of one model and figure out how to express it in terms of the other.
从第一个翻译(路线规范→位置代码列表)开始,我们必须思考列表中位置序列的含义。列表中的第一个位置将是路径的起点,然后路径将被迫依次经过每个位置,直到到达列表中的最后一个位置。因此,出发地和目的地是列表中的第一个和最后一个,海关清关地点(如果有)位于中间。
Starting with the first translation (Route Specification → List of location codes), we have to think about the meaning of the sequence of locations in the list. The first in the list will be the beginning of the path, which will then be forced to pass through each location in turn until it reaches the last location in the list. So the origin and destination are the first and last in the list, with the customs clearance location (if there is one) in the middle.
图 14.4. 将查询翻译成网络遍历服务
Figure 14.4. Translation of a query to the Network Traversal Service
(幸运的是,两支球队使用了相同的位置代码,所以我们不必处理这种级别的翻译。)
(Mercifully, the two teams used the same location codes, so we don’t have to deal with that level of translation.)
请注意,反向翻译会产生歧义,因为网络遍历输入允许任意数量的中间点,而不仅仅是一个专门指定为通关点的中间点。幸运的是,这没有问题,因为我们不需要朝那个方向翻译,但它让我们瞥见了为什么有些翻译是不可能的。
Notice that the reverse translation would be ambiguous, because the network traversal input allows any number of intermediate points, not just one specifically designated as customs clearance point. Fortunately, this is no problem because we don’t need to translate in that direction, but it gives a glimpse of why some translations are impossible.
现在,让我们转换结果(节点ID列表→行程)。我们假设我们可以使用 REPOSITORY根据收到的节点ID 查找节点和运输操作对象。那么,这些节点如何映射到航程?根据,我们可以将节点列表分成出发/到达对。然后每对与一个航程相关。operationType-Code
Now, let’s translate the result (List of Node IDs → Itinerary). We’ll assume that we can use a REPOSITORY to look up the Node and Shipping Operation objects based on the Node IDs we receive. So, how do those Nodes map to Legs? Based on the operationType-Code, we can break the list of Nodes into departure/arrival pairs. Each pair then relates to one Leg.
图 14.5.网络遍历服务找到的路线翻译
Figure 14.5. Translation of a route found by the Network Traversal Service
每个节点对的属性将映射如下:
The attributes for each Node pair would be mapped as follows:
离境节点.shippingOperation.vesselVoyageId → leg.vesselVoyageId
离境节点.shippingOperation.date → leg.loadDate
离境节点.locationCode → leg.loadLocationCode
到达节点.shippingOperation.date → leg.unloadDate
到达节点.locationCode → leg.unloadLocationCode
departureNode.shippingOperation.vesselVoyageId → leg.vesselVoyageId
departureNode.shippingOperation.date → leg.loadDate
departureNode.locationCode → leg.loadLocationCode
arrivalNode.shippingOperation.date → leg.unloadDate
arrivalNode.locationCode → leg.unloadLocationCode
这是这两个模型之间的概念转换图。现在我们必须实现一些可以为我们进行转换的东西。在这种简单的情况下,我通常会为此目的创建一个对象,然后查找或创建另一个对象来为子系统的其余部分提供服务。
This is the conceptual translation map between these two models. Now we have to implement something that can do the translation for us. In a simple case like this, I typically create an object for the purpose, and then find or create another object to provide the service to the rest of our subsystem.
图 14.6. 双向转换器
Figure 14.6. A two-way translator
这是两个团队必须共同维护的一个对象。设计应该使其非常易于进行单元测试,并且团队合作开发测试套件将是一个特别好的主意。除此之外,他们可以各奔东西。
This is the one object that both teams have to work together to maintain. The design should make it very easy to unit-test, and it would be a particularly good idea for the teams to collaborate on a test suite for it. Other than that, they can go their separate ways.
图 14.7
Figure 14.7
现在,路由服务的实现变成了委托给转换器和网络遍历服务。它的单个操作看起来如下所示:
The Routing Service implementation now becomes a matter of delegating to the Translator and the Network Traversal Service. Its single operation would look something like this:
public Itinerary route(RouteSpecification spec) {
Booking_TransportNetwork_Translator translator =
new Booking_TransportNetwork_Translator();
List constrainedLocations =
translator.convertConstraints(spec);
// 获取 NetworkTraversalService 的访问权限
List pathNodes =
traversalService.findPath(constraintLocations);
Itinerary result = translator.convert(pathNodes);
return result;
}
public Itinerary route(RouteSpecification spec) {
Booking_TransportNetwork_Translator translator =
new Booking_TransportNetwork_Translator();
List constraintLocations =
translator.convertConstraints(spec);
// Get access to the NetworkTraversalService
List pathNodes =
traversalService.findPath(constraintLocations);
Itinerary result = translator.convert(pathNodes);
return result;
}
还不错。有界上下文有助于保持每个模型相对清晰,让团队基本独立工作,如果最初的假设是正确的,那么效果可能会很好。(我们将在本章后面讨论这个问题。)
Not bad. The BOUNDED CONTEXTS served to keep each of the models relatively clean, let the teams work largely independently, and if initial assumptions had been correct, would probably have served well. (We’ll return to that later in this chapter.)
两个上下文之间的接口相当小。路由服务的接口将 Booking CONTEXT的其余设计与路线查找世界中的事件隔离开来。该接口易于测试,因为它由无副作用的函数组成。与其他CONTEXT舒适共存的秘诀之一是对接口进行有效的测试。“信任,但要核实”,里根总统在谈判裁军时说。1
The interface between the two contexts is fairly small. The interface of the Routing Service insulates the rest of the Booking CONTEXT’s design from events in the route-finding world. The interface is easy to test because it is made up of SIDE-EFFECT-FREE FUNCTIONS. One of the secrets to comfortable coexistence with other CONTEXTS is to have effective sets of tests for the interfaces. “Trust, but verify,” said President Reagan when negotiating arms reductions.1
设计一组自动化测试应该很容易,这些测试将路线规范输入路线服务并检查返回的行程。
It should be easy to devise a set of automated tests that would feed Route Specifications into the Routing Service and check the returned Itinerary.
模型上下文始终存在,但如果不加注意,它们可能会重叠和波动。通过明确定义有界上下文和上下文图,您的团队可以开始指导统一模型和连接不同模型的过程。
Model contexts always exist, but without conscious attention they may overlap and fluctuate. By explicitly defining BOUNDED CONTEXTS and a CONTEXT MAP, your team can begin to direct the process of unifying models and connecting distinct ones.
与其他有界上下文的接触点对于测试尤为重要。测试有助于弥补翻译的微妙之处以及边界处通常存在的较低层次的沟通。它们可以充当有价值的预警系统,尤其是在您依赖无法控制的模型细节的情况下,它尤其令人放心。
Contact points with other BOUNDED CONTEXTS are particularly important to test. Tests help compensate for the subtleties of translation and the lower level of communication that typically exist at boundaries. They can act as a valuable early warning system, especially reassuring in cases where you depend on the details of a model you don’t control.
这里只有两点重点:
There are only two important points here:
1.有界上下文应该有名字,这样你就可以谈论它们。这些名字应该进入团队的通用语言。
1. The BOUNDED CONTEXTS should have names so that you can talk about them. Those names should enter the UBIQUITOUS LANGUAGE of the team.
2.每个人都必须知道界限在哪里,并且能够识别任何代码或任何情况的上下文。
2. Everyone has to know where the boundaries lie, and be able to recognize the CONTEXT of any piece of code or any situation.
第二个要求可以通过多种方式满足,具体取决于团队的文化。一旦定义了有界上下文,就会自然而然地将不同上下文的代码隔离到不同的模块中,这就留下了一个问题:如何跟踪哪个模块属于哪个上下文。可以使用命名约定来指示这一点,或者使用任何其他简单且避免混淆的机制。
The second requirement could be met in many ways depending on the culture of the team. Once the BOUNDED CONTEXTS have been defined, it comes naturally to segregate the code of different CONTEXTS into different MODULES, which leaves the question of how to keep track of which MODULE belongs in which CONTEXT. A naming convention might be used to indicate this, or any other mechanism that is easy and avoids confusion.
同样重要的是,以团队中每个人都能理解的方式传达概念边界。出于这种沟通目的,我喜欢像示例中这样的非正式图表。可以制作更严格的图表或文本列表,显示每个CONTEXT中的所有包,以及联系点和负责连接和转换的机制。有些团队会更喜欢这种方法,而其他团队会根据口头协议和大量讨论顺利完成任务。
Equally important is communicating the conceptual boundaries in such a way that everyone on the team understands them the same way. For this communication purpose, I like informal diagrams like the ones in the example. More rigorous diagrams or textual lists could be made, showing all packages in each CONTEXT, along with the points of contact and the mechanisms responsible for connecting and translating. Some teams will be more comfortable with this approach, while others will get by fine based on spoken agreement and lots of discussion.
无论如何,如果要将名称纳入UBIQUITOUS LANGUAGE ,则将CONTEXT MAP纳入讨论中至关重要。不要说:“George 团队的内容正在发生变化,因此我们必须更改与之对话的内容。”而应该说:“运输网络模型正在发生变化,因此我们必须更改Booking 上下文的翻译器。”
In any case, working the CONTEXT MAP into discussions is essential if the names are to enter the UBIQUITOUS LANGUAGE. Don’t say, “George’s team’s stuff is changing, so we’re going to have to change our stuff that talks to it.” Say instead, “The Transport Network model is changing, so we’re going to have to change the translator for the Booking context.”
以下模式涵盖了一系列用于关联两个模型的策略,这些模型可以组合起来以涵盖整个企业。这些模式具有双重目的:提供成功组织开发工作的目标,并提供描述现有组织的词汇。
The following patterns cover a range of strategies for relating two models that can be composed to encompass an entire enterprise. These patterns serve the dual purpose of providing targets for successfully organizing development work, and supplying vocabulary for describing the existing organization.
现有的关系可能偶然或有意接近这些模式之一,在这种情况下,您可以使用该术语描述它,并适当注意变化。然后,随着每次小的设计变化,关系可以更接近所选模式。
An existing relationship may, by chance or by design, fall near one of these patterns, in which case you can describe it using that term, variations duly noted. Then, with each small design change, the relationship can be drawn closer to the chosen pattern.
另一方面,你可能会发现现有的关系混乱或过于复杂。为了使明确的上下文图成为可能,可能需要进行一些重组。在这种情况下,或者在你考虑重组的任何情况下,这些模式提供了一系列在不同情况下更受青睐的选择。突出的变量包括您对另一个模型的控制级别、团队之间的合作级别和类型以及特征和数据的集成程度。
On the other hand, you may find that an existing relationship is muddled or overcomplicated. Some reorganization might be necessary just to make an unambiguous CONTEXT MAP possible. In this situation, or any situation in which you are considering reorganization, these patterns present a range of choices that are favored in different circumstances. Prominent variables include the level of control you have over the other model, the level and type of cooperation between teams, and the degree of integration of features and data.
以下模式集涵盖了一些最常见和最重要的情况,它们应该可以让您很好地了解如何处理其他情况。一个紧密合作开发紧密集成产品的精干团队可以部署大型统一模型。服务不同用户社区的需求或团队协调能力的限制可能会导致共享内核或客户/供应商关系。有时,仔细审视需求会发现集成并非必不可少,系统最好各行其道。当然,大多数项目必须在一定程度上与遗留系统和外部系统集成,这可能导致开放主机服务或反腐败层。
The following set of patterns covers some of the most common and important cases, which should give you a good idea of how to approach other cases. A crack team working closely on a tightly integrated product can deploy a large unified model. The need to serve different user communities or a limitation on the coordination abilities of the team might lead to a SHARED KERNEL or CUSTOMER/SUPPLIER relationships. Sometimes a good hard look at the requirements reveals that integration is not essential and it is best for systems to go their SEPARATE WAYS. And, of course, most projects have to integrate to some degree with legacy and external systems, which can lead to OPEN HOST SERVICES or ANTICORRUPTION LAYERS.
当功能集成受到限制时,持续集成的开销可能被认为过高。当团队没有技能或政治组织来维持持续集成时,或者当单个团队太大且难以管理时,情况尤其如此。因此,可以定义单独的有界上下文并组建多个团队。
When functional integration is limited, the overhead of CONTINUOUS INTEGRATION may be deemed too high. This may especially be true when the teams do not have the skill or the political organization to maintain continuous integration, or when a single team is simply too big and unwieldy. So separate BOUNDED CONTEXTS might be defined and multiple teams formed.
缺乏协调的团队在密切相关的应用程序上工作,虽然可以快速前进一段时间,但他们生产的产品可能并不契合。他们最终在翻译层和改造上花费的钱可能比在持续集成上花费的钱还要多,同时重复劳动并失去通用通用语言的好处。
Uncoordinated teams working on closely related applications can go racing forward for a while, but what they produce may not fit together. They can end up spending more on translation layers and retrofitting than they would have on CONTINUOUS INTEGRATION in the first place, meanwhile duplicating effort and losing the benefits of a common UBIQUITOUS LANGUAGE.
在许多项目中,我看到基础设施层在独立工作的团队之间共享。类似的做法在领域内也适用。完全同步整个模型和代码库的开销可能太大,但精心选择的子集可以以较低的成本提供很多好处。
On many projects I’ve seen the infrastructure layer shared among teams that worked largely independently. An analogy to this can work well within the domain as well. It may be too much overhead to fully synchronize the entire model and code base, but a carefully selected subset can provide much of the benefit for less cost.
所以:
Therefore:
指定两个团队同意共享的领域模型的某个子集。当然,这包括模型,即与该部分模型相关的代码子集或数据库设计子集。这些明确共享的内容具有特殊地位,未经与其他团队协商不得更改。
Designate some subset of the domain model that the two teams agree to share. Of course this includes, along with this subset of the model, the subset of code or of the database design associated with that part of the model. This explicitly shared stuff has special status, and shouldn’t be changed without consultation with the other team.
频繁集成功能系统,但频率要比团队内部持续集成的速度略低。在这些集成过程中,运行两个团队的测试。
Integrate a functional system frequently, but somewhat less often than the pace of CONTINUOUS INTEGRATION within the teams. At these integrations, run the tests of both teams.
这是一种谨慎的平衡。共享内核不能像设计的其他部分那样自由更改。决策需要与另一个团队协商。必须集成自动测试套件,因为在进行更改时,两个团队的所有测试都必须通过。通常,团队在内核的不同副本上进行更改,并定期与其他团队集成。(例如,在每天或更早进行持续集成的团队中,内核合并可能是每周一次。)无论代码集成安排在何时,两个团队越早讨论更改越好。
It is a careful balance. The SHARED KERNEL cannot be changed as freely as other parts of the design. Decisions involve consultation with another team. Automated test suites must be integrated because all tests of both teams must pass when changes are made. Usually, teams make changes on separate copies of the KERNEL, integrating with the other team at intervals. (For example, on a team that CONTINUOUSLY INTEGRATES daily or better, the KERNEL merger might be weekly.) Regardless of when code integration is scheduled, the sooner both teams talk about the changes, the better.
SHARED KERNEL通常是CORE DOMAIN 、某些GENERIC SUBDOMAINS集,或者两者兼而有之(参见第 15 章),但它可以是两个团队都需要的模型的任何部分。目标是减少重复(但不是消除重复,如果只有一个BOUNDED CONTEXT,情况就会如此),并使两个子系统之间的集成相对容易。
The SHARED KERNEL is often the CORE DOMAIN, some set of GENERIC SUBDOMAINS, or both (see Chapter 15), but it can be any part of the model that is needed by both teams. The goal is to reduce duplication (but not to eliminate it, as would be the case if there were just one BOUNDED CONTEXT) and make integration between the two subsystems relatively easy.
通常,一个子系统本质上是另一个子系统的信息来源;“下游”组件执行分析或其他功能,而这些功能很少反馈给“上游”组件,所有依赖关系都是单向的。这两个子系统通常服务于非常不同的用户群体,他们从事不同的工作,因此不同的模型可能有用。工具集也可能不同,因此程序代码无法共享。
Often one subsystem essentially feeds another; the “downstream” component performs analysis or other functions that feed back very little into the “upstream” component, and all dependencies go one way. The two subsystems commonly serve very different user communities, who do different jobs, where different models may be useful. The tool set may also be different, so that program code cannot be shared.
上游和下游子系统自然分为两个有界上下文。当两个组件需要不同的技能或使用不同的工具集进行实施时,尤其如此。由于只需朝一个方向操作,因此翻译更容易。但问题可能会出现,这取决于两个团队的政治关系。
Upstream and downstream subsystems separate naturally into two BOUNDED CONTEXTS. This is especially true when the two components require different skills or employ a different tool set for implementation. Translation is easier for having to operate in one direction only. But problems can emerge, depending on the political relationship of the two teams.
如果下游团队对变更拥有否决权,或者请求变更的程序过于繁琐,上游团队的自由开发可能会受到限制。上游团队甚至可能会受到抑制,担心会破坏下游系统。与此同时,下游团队可能束手无策,只能听从上游的优先顺序。
The freewheeling development of the upstream team can be cramped if the downstream team has veto power over changes, or if procedures for requesting changes are too cumbersome. The upstream team may even be inhibited, worried about breaking the downstream system. Meanwhile, the downstream team can be helpless, at the mercy of upstream priorities.
下游需要上游的东西,但上游不负责下游的交付物。这需要很多额外的努力预测什么会影响另一个团队,人性就是这样,时间压力也是这样,好吧……正式确定团队之间的关系会让每个人的工作都更轻松。可以组织该流程以平衡两个用户社区的需求并安排下游所需功能的工作。
Downstream needs things from upstream, but upstream is not responsible for downstream deliverables. It takes a lot of extra effort to anticipate what will affect the other team, and human nature being what it is, and time pressures being what they are, well . . . . It makes everyone’s life easier to formalize the relationship between the teams. The process can be organized to balance the needs of the two user communities and schedule work on features needed downstream.
在极限编程项目中,已经存在一种机制来实现这一点:迭代规划流程。我们所要做的就是根据规划流程定义两个团队之间的关系。下游团队的代表可以像用户代表一样发挥作用,加入他们的规划会议,直接与其他“客户”讨论他们想要的任务的权衡。结果是供应商团队的迭代计划包括下游团队最需要的任务或根据协议推迟的任务,因此没有交付期望。
On an Extreme Programming project, there already is a mechanism in place for doing just that: the iteration planning process. All we have to do is define the relationship between the two teams in terms of the planning process. Representatives of the downstream team can function much like the user representatives, joining them in planning sessions, discussing directly with their fellow “customers” the trade-offs for the tasks they want. The result is an iteration plan for the supplier team that includes tasks the downstream team needs most or defers tasks by agreement, so there is no expectation of delivery.
如果使用 XP 以外的流程,则任何用于平衡不同用户关注点的类似方法都可以扩展以包括下游应用程序的需求。
If a process other than XP is used, whatever analogous method serves to balance the concerns of different users can be expanded to include the downstream application’s needs.
所以:
Therefore:
在两个团队之间建立明确的客户/供应商关系。在规划会议中,让下游团队扮演上游团队的客户角色。协商并预算下游需求的任务,以便每个人都了解承诺和时间表。
Establish a clear customer/supplier relationship between the two teams. In planning sessions, make the downstream team play the customer role to the upstream team. Negotiate and budget tasks for downstream requirements so that everyone understands the commitment and schedule.
共同开发自动化验收测试,以验证预期的接口。将这些测试添加到上游团队的测试套件中,作为其持续集成的一部分运行。此测试将使上游团队可以自由地进行更改,而不必担心下游的副作用。
Jointly develop automated acceptance tests that will validate the interface expected. Add these tests to the upstream team’s test suite, to be run as part of its continuous integration. This testing will free the upstream team to make changes without fear of side effects downstream.
在迭代过程中,下游团队成员需要像传统客户一样为上游开发人员提供服务,回答问题并帮助解决问题。
During the iteration, the downstream team members need to be available to the upstream developers just as conventional customers are, to answer questions and help resolve problems.
自动化验收测试是客户关系中至关重要的一部分。即使在最具合作性的项目中,尽管客户可以识别和传达其依赖关系,供应商也可以努力传达变更,但如果没有测试,意外就会发生。它们会扰乱下游团队的工作,并迫使上游团队进行计划外的紧急修复。相反,让客户团队与供应商团队合作,开发自动化验收测试,以验证其期望的接口。上游团队将作为其标准测试套件的一部分运行这些测试。对这些测试的任何更改都需要与其他团队进行沟通,因为更改测试意味着更改接口。
Automating the acceptance tests is a vital part of this customer relationship. Even on the most cooperative project, although the customer can identify and communicate its dependencies, and the supplier can diligently try to communicate changes, without tests, surprises will happen. They will disrupt the downstream team’s work and force the upstream team to take on unscheduled, emergency fixes. Instead, have the customer team, in collaboration with the supplier team, develop automated acceptance tests that will validate the interface it expects. The upstream team will run these tests as part of its standard test suite. Any change to these tests calls for communication with the other team, because changing the tests implies changing the interface.
客户/供应商关系也出现在不同公司的项目之间,当某个客户对供应商的业务非常重要时。尾随者可能会摇动狗:有影响力的客户可能会提出对上游项目成功至关重要的要求,但这些要求也可能破坏上游项目的发展。双方都受益于响应需求的流程的正规化,因为成本/收益权衡在外部关系中比在内部 IT 环境中更难看到。
Customer/supplier relationships also emerge between projects in separate companies, in situations where a single customer is very important to the business of the supplier. The tail can wag the dog: an influential customer can make demands that are important to the upstream project’s success, but those demands can also be disruptive to the upstream project’s development. Both parties benefit from the formalization of the process of responding to requirements, because the cost/benefit trade-offs are even harder to see in external relationships than they are in the internal IT situation.
这种模式有两个关键要素。
There are two crucial elements to this pattern.
1.必须是客户与供应商的关系,这意味着客户的需求是最重要的。由于下游团队不是唯一的客户,因此必须在谈判中平衡不同客户的需求——但它们仍然是优先事项。这种情况与经常出现的“穷表兄弟”关系形成鲜明对比,在这种关系中,下游团队不得不向上游团队乞求自己的需求。
1. The relationship must be that of customer and supplier, with the implication that the customer’s needs are paramount. Because the downstream team is not the only customer, the different customers’ demands have to be balanced in negotiation—but they remain priorities. This situation is in contrast to the poor-cousin relationship that often emerges, in which the downstream team has to come begging to the upstream team for its needs.
2.必须有一个自动化测试套件,允许上游团队更改其代码而不必担心破坏下游,并让下游团队专注于自己的工作,而无需不断监视上游团队。
2. There must be an automated test suite that allows the upstream team to change its code without fear of breaking the downstream, and lets the downstream team concentrate on its own work without constantly monitoring the upstream team.
在接力赛中,跑在前面的人不能一直回头看,检查情况。他或她必须能够相信接力棒的接力手能够准确地交接,否则整个团队的速度就会被严重拖慢。
In a relay race, the forward runner can’t be looking backward all the time, checking. He or she has to be able to trust the baton carrier to make the handoff precisely, or else the team will be hopelessly slowed down.
回到我们值得信赖的航运案例。一个高度专业的团队已经成立,负责分析公司的所有订单,以了解如何实现收入最大化。团队成员可能会发现船只有空位,并可能建议更多超额预订。他们可能会发现船舶提前装满了散装货物,迫使公司拒绝运输利润更高的特种货物。在这种情况下,他们可能会建议为这些类型的货物预留空间或提高散装货物的价格。
Back to our trusty shipping example. A highly specialized team has been set up to analyze all the bookings that flow through the firm, to see how to maximize income. Team members might find that ships have empty space and might recommend more overbooking. They might find that the ships are filling up with bulk freight early, forcing the company to turn away more lucrative specialty cargoes. In that case they might recommend reserving space for these types of cargo or raising prices on the bulk freight.
为了进行分析,他们使用自己的复杂模型。为了实施,他们使用带有构建分析模型工具的数据仓库。而且他们需要来自 Booking 应用程序的大量信息。
To do this analysis, they use their own complex models. For implementation, they use a data warehouse with tools for building analytical models. And they need lots of information from the Booking application.
从一开始,就可以看出这是两个BOUNDED CONTEXTS,因为它们使用不同的实现工具,最重要的是,不同的领域模型。它们之间应该是什么关系?
From the start, it is clear that these are two BOUNDED CONTEXTS, because they use different implementation tools and, most important, different domain models. What should the relationship between them be?
SHARED KERNEL似乎合乎逻辑,因为收益分析对 Booking 模型的子集感兴趣,而他们自己的模型在货物、价格等方面有一些重叠的概念。但是,在使用不同的实现技术的情况下, SHARED KERNEL会很困难。此外,收益分析团队的建模需求非常专业,他们不断地使用自己的模型并尝试替代模型。他们最好将 Booking CONTEXT中所需的内容转换为自己的内容。(另一方面,如果他们可以使用SHARED KERNEL,他们的翻译负担就会轻得多。他们仍然必须重新实现模型并将数据转换为新的实现,但如果模型相同,则转移应该很简单。)
A SHARED KERNEL might seem logical, because yield analysis is interested in a subset of the Booking’s model, and their own model has some overlapping concepts of cargos, prices, and so on. But SHARED KERNEL is difficult in a case where different implementation technologies are being used. Besides, the modeling needs of the yield analysis team are quite specialized, and they continuously play with their models and try alternative ones. They may well be better off translating what they need from the Booking CONTEXT into their own. (On the other hand, if they can use a SHARED KERNEL, their translation burden will be much lighter. They will still have to reimplement the model and translate the data to the new implementation, but if the model is the same, the transfer should be simple.)
Booking 应用程序不依赖收益分析,因为它无意自动调整政策。人类专家将做出决定并将其传达给所需的人员和系统。所以我们有一个上下游关系。下游需要的是:
The Booking application has no dependency on the yield analysis, because there is no intention of automatically adjusting policies. Human specialists will make the decisions and convey them to the needed people and systems. So we have an upstream/downstream relationship. What downstream needs is this:
1.某些数据并非任何预订操作所必需
1. Some data not needed by any booking operation
2.数据库模式有一定的稳定性(或至少有可靠的变更通知)或导出实用程序
2. Some stability in database schema (or at least reliable notification of change) or an export utility
幸运的是,Booking 应用程序开发团队的项目经理愿意帮助收益分析团队。这可能是一个问题,因为实际负责日常预订的运营部门向不同的副总裁报告,而不是向收益分析团队报告。实际进行收益分析的人员。但高层管理人员非常关心收益管理,并且看到了过去两个部门之间的合作问题,因此对软件开发项目进行了结构化,以便两个团队的项目经理都向同一个人汇报。
Fortunately, the project manager of the Booking application development team is motivated to help the yield analysis team. This could have been a problem, because the operations department that actually does day-to-day booking reports to a different vice president than the people who actually do yield analysis. But the upper management cares deeply about yield management and, having seen past cooperation problems between the two departments, structured the software development project so that the project managers of both teams report to the same person.
因此,所有要求均已到位,可以应用客户/供应商开发团队。
Therefore, all the requirements are in place to apply CUSTOMER/SUPPLIER DEVELOPMENT TEAMS.
我曾在多个地方看到过这种情况,分析软件开发人员和运营软件开发人员之间存在客户/供应商关系。当上游团队成员将自己的角色视为服务客户时,事情就会进展得相当顺利。它几乎总是非正式地组织起来的,在每种情况下,它的效果都与两位项目经理之间的个人关系一样好。
I’ve seen this scenario evolve in multiple places, where analysis software developers and operations software developers had a customer/supplier relationship. When the upstream team members thought of their role as serving a customer, things worked out pretty well. It was almost always organized informally, and in each case it worked out about as well as the personal relationship of the two project managers.
在一个 XP 项目中,我看到这种关系正式化,即对于每个迭代,下游团队的代表都扮演客户的角色,与更传统的客户代表(应用程序功能)聚在一起,协商哪些任务可以纳入迭代计划。这个项目在一家小公司进行,因此最近的共同老板离上层不远。这种方法效果很好。
On one XP project, I saw this relationship formalized in the sense that, for each iteration, representatives of the downstream team played the “planning game” in the role of customers, huddling with the more conventional customer representatives (of application functionality) to negotiate which tasks made it into the iteration plan. This project was at a small company, and so the nearest shared boss was not far up the chain. It worked very well.
如果客户/供应商团队在同一管理层下工作,最终他们确实有共同的目标,或者他们身处不同的公司,但实际上这些公司都承担着这些角色,那么客户/供应商团队的成功几率就会更高。当上游团队没有动力时,情况就大不相同了。...
CUSTOMER/SUPPLIER TEAMS are more likely to succeed if the two teams work under the same management, so that ultimately they do share goals, or where they are in different companies that actually have those roles. When there is nothing to motivate the upstream team, the situation is very different. . . .
当两个具有上下游关系的团队没有得到来自同一来源的有效指导时,客户/供应商团队等合作模式将无法发挥作用。天真地尝试应用它将使下游团队陷入困境。这种情况可能发生在一家大公司中,两个团队在管理层级中相距甚远,或者共享主管对两个团队的关系漠不关心。当客户的业务对供应商来说并不重要时,不同公司的团队之间也会出现这种情况。也许供应商有很多小客户,或者供应商正在改变市场方向,不再重视老客户。供应商可能经营不善。它可能已经倒闭了。不管是什么原因,现实情况是下游只能靠自己。
When two teams with an upstream/downstream relationship are not effectively being directed from the same source, a cooperative pattern such as CUSTOMER/SUPPLIER TEAMS is not going to work. Naively trying to apply it will get the downstream team into trouble. This can be the case in a large company in which the two teams are far apart in the management hierarchy or where the shared supervisor is indifferent to the relationship of the two teams. It also arises between teams in different companies when the customer’s business is not individually important to the supplier. Perhaps the supplier has many small customers, or perhaps the supplier is changing market direction and no longer values the old customers. The supplier may just be poorly run. It may have gone out of business. Whatever the reason, the reality is that the downstream is on its own.
当两个开发团队之间存在上下游关系时,上游没有动力满足下游团队的需求,下游团队将无能为力。利他主义可能会促使上游开发人员做出承诺,但这些承诺不太可能实现。对这些良好意图的信任会导致下游团队根据永远不会提供的功能制定计划。下游项目将被推迟,直到团队最终学会适应所提供的东西。根据下游团队的需求量身定制的界面是不可能的。
When two development teams have an upstream/downstream relationship in which the upstream has no motivation to provide for the downstream team’s needs, the downstream team is helpless. Altruism may motivate upstream developers to make promises, but they are unlikely to be fulfilled. Belief in those good intentions leads the downstream team to make plans based on features that will never be available. The downstream project will be delayed until the team ultimately learns to live with what it is given. An interface tailored to the needs of the downstream team is not in the cards.
在这种情况下,有三种可能的路径。一种是完全放弃使用上游。应现实地评估此选项,不要假设上游会满足下游需求。有时我们会高估这种依赖的价值或低估其成本。如果下游团队决定切断联系,他们就会分道扬镳(请参阅本章后面的模式描述)。
In this situation, there are three possible paths. One is to abandon use of the upstream altogether. This option should be evaluated realistically, making no assumptions that the upstream will accommodate downstream needs. Sometimes we overestimate the value or underestimate the cost of such a dependency. If the downstream team decides to cut the strings, they are going their SEPARATE WAYS (see the pattern description later in this chapter).
有时使用上游软件的价值如此之大,以至于必须保持依赖关系(或者已经做出了团队无法更改的政治决定)。在这种情况下,两条路径仍然开放;选择取决于上游设计的质量和风格。如果设计很难使用,可能是由于缺乏封装、笨拙的抽象或以团队无法使用的范式建模,那么下游团队仍然需要开发自己的模型。他们将不得不对可能很复杂的转换层承担全部责任。(请参阅本章后面的ANTICORRUPTION LAYER 。)
Sometimes the value of using the upstream software is so great that the dependency has to be maintained (or a political decision has been made that the team cannot change). In this case, two paths remain open; the choice depends on the quality and style of the upstream design. If the design is very difficult to work with, perhaps for lack of encapsulation, awkward abstractions, or modeling in a paradigm the team cannot use, then the downstream team will still need to develop its own model. They will have to take full responsibility for a translation layer that is likely to be complex. (See ANTICORRUPTION LAYER, later in this chapter.).
另一方面,如果质量不是太差,风格也还算合适,那么最好还是放弃独立模型吧。这种情况需要CONFORMIST。
On the other hand, if the quality is not so bad, and the style is reasonably compatible, then it may be best to give up on an independent model altogether. This is the circumstance that calls for a CONFORMIST.
所以:
Therefore:
通过严格遵循上游团队的模型来消除BOUNDED CONTEXTS之间的转换复杂性。虽然这会限制下游设计师的风格,并且可能不会产生理想的应用程序模型,但选择CONFORMITY可以极大地简化集成。此外,您将与供应商团队共享一种UBIQUITOUS LANGUAGE。供应商处于主导地位,因此最好让他们轻松沟通。利他主义可能足以让他们与您分享信息。
Eliminate the complexity of translation between BOUNDED CONTEXTS by slavishly adhering to the model of the upstream team. Although this cramps the style of the downstream designers and probably does not yield the ideal model for the application, choosing CONFORMITY enormously simplifies integration. Also, you will share a UBIQUITOUS LANGUAGE with your supplier team. The supplier is in the driver’s seat, so it is good to make communication easy for them. Altruism may be sufficient to get them to share information with you.
这一决定加深了您对上游的依赖,并将您的应用程序限制在上游模型的功能范围内,外加纯粹的附加增强功能。这在情感上非常没有吸引力,这就是为什么我们选择它的频率低于我们应该选择的频率。
This decision deepens your dependency on the upstream and limits your application to the capabilities of the upstream model—plus purely additive enhancements. It is very unappealing emotionally, which is why we choose it less often than we probably should.
如果这些权衡是不可接受的,但上游依赖又是不可或缺的,那么第二种选择仍然存在:通过创建ANTICORRUPTION LAYER尽可能地保护自己,这是一种积极的实现转换图的方法,稍后将讨论。
If these trade-offs are not acceptable, but the upstream dependency is indispensable, the second option still remains: Insulate yourself as much as possible by creating an ANTICORRUPTION LAYER, an aggressive approach to implementing a translation map that will be discussed later.
CONFORMIST与SHARED KERNEL相似之处在于,两者都有重叠区域,其中模型相同,您的模型通过添加进行了扩展,而其他模型不会影响您。这两种模式的区别在于决策和开发过程。SHARED KERNEL是两个紧密协作的团队之间的协作,而CONFORMIST 则与对协作不感兴趣的团队进行集成。
CONFORMIST resembles SHARED KERNEL in that both have an overlapping area where the model is the same, areas where your model has been extended by addition, and areas where the other model does not affect you. The difference between the patterns is in the decision-making and development processes. Where the SHARED KERNEL is a collaboration between two teams that coordinate tightly, CONFORMIST deals with integration with a team that is not interested in collaboration.
我们一直在研究有界环境之间集成的一系列合作,从高度合作的共享内核或客户/供应商开发团队到单方面的顺从者。现在,我们将采取最后一步,对这种关系采取更为悲观的看法,假设另一方既没有合作,也没有可用的设计。……
We’ve been proceeding down a spectrum of cooperation in the integration between BOUNDED CONTEXTS, from highly cooperative SHARED KERNELS or CUSTOMER/SUPPLIER DEVELOPER TEAMS to the one-sidedness of the CONFORMIST. Now we’ll take the final step to an even more pessimistic view of the relationship, assuming neither cooperation nor a usable design on the other side. . . .
新系统几乎总是必须与具有自己模型的旧系统或其他系统集成。当将精心设计的有界上下文与合作团队连接起来时,翻译层可以很简单,甚至很优雅。但是当边界的另一侧开始渗透时,翻译层可能会呈现出更具防御性的语气。
New systems almost always have to be integrated with legacy or other systems, which have their own models. Translation layers can be simple, even elegant, when bridging well-designed BOUNDED CONTEXTS with cooperative teams. But when the other side of the boundary starts to leak through, the translation layer may take on a more defensive tone.
当构建一个必须与另一个系统有大型接口的新系统时,将两个模型关联起来的难度最终会压倒新模型的意图,导致它被临时修改为类似于另一个系统的模型。遗留系统的模型通常很弱,即使是开发良好的例外也可能不符合当前项目的需求。然而,集成可能有很多价值,有时它是绝对必要的。
When a new system is being built that must have a large interface with another, the difficulty of relating the two models can eventually overwhelm the intent of the new model altogether, causing it to be modified to resemble the other system’s model, in an ad hoc fashion. The models of legacy systems are usually weak, and even the exception that is well developed may not fit the needs of the current project. Yet there may be a lot of value in the integration, and sometimes it is an absolute requirement.
答案并不是避免与其他系统进行集成。我曾经参与过一些项目,人们热情地着手替换所有遗留系统,但这实在是太难了,无法一下子完成。此外,与现有系统集成是一种有价值的重用形式。在一个大型项目中,一个子系统通常必须与其他几个独立开发的子系统进行交互。这些子系统将以不同的方式反映问题领域。当基于不同模型的系统组合在一起时,新系统需要适应其他系统的语义,这可能会导致新系统自身的语义损坏模型。即使对方系统设计得很好,它也不是基于与客户相同的模型。而且对方系统通常设计得并不好。
The answer is not to avoid all integration with other systems. I’ve been on projects where people enthusiastically set out to replace all the legacy, but this is just too much to take on at once. Besides, integrating with existing systems is a valuable form of reuse. On a large project, one subsystem will often have to interface with several other, independently developed subsystems. These will reflect the problem domain differently. When systems based on different models are combined, the need for the new system to adapt to the semantics of the other system can lead to a corruption of the new system’s own model. Even when the other system is well designed, it is not based on the same model as the client. And often the other system is not well designed.
与外部系统交互时会遇到许多障碍。例如,基础架构层必须提供与可能位于不同平台或使用不同协议的另一个系统进行通信的方法。另一个系统的数据类型必须转换为您的系统的数据类型。但经常被忽视的是,另一个系统肯定不会使用相同的概念域模型。
There are many hurdles in interfacing with an external system. For example, the infrastructure layer must provide the means to communicate with another system that might be on a different platform or use different protocols. The data types of the other system must be translated into those of your system. But often overlooked is the certainty that the other system does not use the same conceptual domain model.
显然,如果你从一个系统获取一些数据,而在另一个系统中对其进行了误解,就会产生错误。你甚至可能会破坏数据库。但即便如此,这个问题往往会悄悄地出现在我们面前,因为我们认为我们在系统之间传输的是原始数据,其含义是明确的,并且在两端必须相同。这种假设通常是错误的。每个系统中的数据关联方式会产生微妙但重要的含义差异。即使原始数据元素确实具有完全相同的含义,让与另一个系统的接口在如此低的级别上运行通常也是一个错误。低级接口剥夺了另一个系统模型解释数据和约束其值和关系的能力,同时让新系统承担解释不属于其自身模型的原始数据的负担。
It seems clear enough that errors will result if you take some data from one system and misinterpret it in another. You may even corrupt the database. But even so, this problem tends to sneak up on us because we think that what we are transporting between systems is primitive data, whose meaning is unambiguous and must be the same on both sides. This assumption is usually wrong. Subtle yet important differences in meaning arise from the way the data are associated in each system. And even if primitive data elements do have exactly the same meaning, it is usually a mistake to make the interface to the other system operate at such a low level. A low-level interface takes away the power of the other system’s model to explain the data and constrain its values and relationships, while saddling the new system with the burden of interpreting primitive data that is not in terms of its own model.
我们需要在遵循不同模型的部分之间提供转换,以便模型不会被外部模型未消化的元素所破坏。
We need to provide a translation between the parts that adhere to different models, so that the models are not corrupted with undigested elements of foreign models.
所以:
Therefore:
创建一个隔离层,为客户提供基于其自身领域模型的功能。该层通过其现有接口与其他系统通信,几乎不需要对其他系统进行任何修改。在内部,该层根据需要在两个模型之间进行双向转换。
Create an isolating layer to provide clients with functionality in terms of their own domain model. The layer talks to the other system through its existing interface, requiring little or no modification to the other system. Internally, the layer translates in both directions as necessary between the two models.
这种关于连接两个系统的机制的讨论可能会让人想到将数据从一个程序传输到另一个程序或从一个服务器传输到另一个服务器的问题。我将很快讨论技术通信机制的整合。但这些细节不应该请勿将其与ANTICORRUPTION LAYER混淆,后者并非向另一个系统发送消息的机制。相反,它是一种将概念对象和操作从一个模型和协议转换到另一个模型和协议的机制。
This discussion of a mechanism to link two systems might bring to mind issues of transporting the data from one program to another or from one server to another. I’ll discuss the incorporation of the technical communications mechanism shortly. But such details shouldn’t be confused with an ANTICORRUPTION LAYER, which is not a mechanism for sending messages to another system. Rather, it is a mechanism that translates conceptual objects and actions from one model and protocol to another.
ANTICORRUPTION LAYER本身就是一种复杂的软件。接下来,我将概述创建 ANTICORRUPTION LAYER 的一些设计注意事项。
An ANTICORRUPTION LAYER can become a complex piece of software in its own right. Next I’ll outline some of the design considerations for creating one.
ANTICORRUPTION LAYER的公共接口通常以一组SERVICES的形式出现,尽管有时它可以采用ENTITY的形式。构建一个负责两个系统语义之间转换的全新层,使我们有机会重新抽象另一个系统的行为,并以与我们的模型一致的方式向我们的系统提供其服务和信息。在我们的模型中,将外部系统表示为单个组件甚至可能毫无意义。最好使用多个SERVICES(或偶尔使用ENTITIES),每个服务在我们的模型中都有一致的职责。
The public interface of the ANTICORRUPTION LAYER usually appears as a set of SERVICES, although occasionally it can take the form of an ENTITY. Building a whole new layer responsible for the translation between the semantics of the two systems gives us an opportunity to reabstract the other system’s behavior and offer its services and information to our system consistently with our model. It may not even make sense, in our model, to represent the external system as a single component. It may be best to use multiple SERVICES (or occasionally ENTITIES), each of which has a coherent responsibility in terms of our model.
组织ANTICORRUPTION LAYER设计的一种方法是将FACADES、ADAPTERS(均来自Gamma et al. 1995)和翻译器与系统间通信通常需要的通信和传输机制结合起来。
One way of organizing the design of the ANTICORRUPTION LAYER is as a combination of FACADES, ADAPTERS (both from Gamma et al. 1995), and translators, along with the communication and transport mechanisms usually needed to talk between systems.
我们经常需要与具有庞大、复杂、混乱接口的系统集成。这是一个实施问题,而不是促使使用ANTICORRUPTION LAYERS的概念模型差异问题,但它是您在尝试创建它们时会遇到的问题。从一个模型转换到另一个模型(特别是当一个模型模糊时)是一项非常困难的工作,同时还要处理难以沟通的子系统接口。幸运的是,这就是FACADES的用途。
We often have to integrate with systems that have large, complicated, messy interfaces. This is an implementation issue, not an issue of conceptual model differences that motivated the use of ANTICORRUPTION LAYERS, but it is a problem you’ll encounter trying to create them. Translating from one model to another (especially if one model is fuzzy) is a hard enough job without simultaneously dealing with a subsystem interface that is hard to talk to. Fortunately, that is what FACADES are for.
FACADE是子系统的替代接口,可简化客户端的访问并使子系统更易于使用。因为我们确切地知道我们想要使用其他系统的哪些功能,所以我们可以创建一个FACADE ,以促进和简化对这些功能的访问并隐藏其余功能。FACADE不会 更改底层系统的模型。它应该严格按照其他系统的模型编写。否则,您最好将翻译的责任分散到多个对象中,并使 FACADE 超载,最坏的情况是最终创建另一个模型,一个不属于其他系统或您自己的BOUNDED CONTEXT的模型。FACADE属于其他系统的BOUNDED CONTEXT。它只是呈现了一个更友好的界面,专门满足您的需求。
A FACADE is an alternative interface for a subsystem that simplifies access for the client and makes the subsystem easier to use. Because we know exactly what functionality of the other system we want to use, we can create a FACADE that facilitates and streamlines access to those features and hides the rest. The FACADE does not change the model of the underlying system. It should be written strictly in accordance with the other system’s model. Otherwise, you will at best diffuse responsibility for translation into multiple objects and overload the FACADE and at worst end up creating yet another model, one that doesn’t belong to the other system or your own BOUNDED CONTEXT. The FACADE belongs in the BOUNDED CONTEXT of the other system. It just presents a friendlier face specialized for your needs.
ADAPTER是一个包装器,它允许客户端使用与行为实现者所理解的协议不同的协议。当客户端向 ADAPTER 发送消息时,该消息将被转换为语义上等效的消息并发送给“适配者”。响应被转换并传回。我使用适配器这个术语有点宽泛,因为Gamma et al. 1995 的重点是使包装对象符合客户端期望的标准接口,而我们可以选择适配的接口,而适配者甚至可能不是一个对象。我们的重点是两个模型之间的转换,但我认为这与ADAPTER的意图是一致的。
An ADAPTER is a wrapper that allows a client to use a different protocol than that understood by the implementer of the behavior. When a client sends a message to an ADAPTER, it is converted to a semantically equivalent message and sent on to the “adaptee.” The response is converted and passed back. I’m using the term adapter a little loosely, because the emphasis in Gamma et al. 1995 is on making a wrapped object conform to a standard interface that clients expect, whereas we get to choose the adapted interface, and the adaptee is probably not even an object. Our emphasis is on translation between two models, but I think this is consistent with the intent of ADAPTER.
对于我们定义的每个服务,我们都需要一个支持该服务接口的适配器 (ADAPTER),并且知道如何对其他系统或其FACADE提出等效请求。
For each SERVICE we define, we need an ADAPTER that supports the SERVICE’S interface and knows how to make equivalent requests of the other system or its FACADE.
剩下的元素是转换器。适配器的工作是知道如何发出请求。概念对象或数据的实际转换是一项独特而复杂的任务,可以放在其自己的对象中,使它们更容易理解。转换器可以是轻量级对象,在需要时实例化。它不需要状态,也不需要分发,因为它属于它所服务的适配器。
The remaining element is the translator. The ADAPTER’S job is to know how to make a request. The actual conversion of conceptual objects or data is a distinct, complex task that can be placed in its own object, making them both much easier to understand. A translator can be a lightweight object that is instantiated when needed. It needs no state and does not need to be distributed, because it belongs with the ADAPTER(S) it serves.
这些是我用来创建ANTICORRUPTION LAYER 的基本元素。还有一些其他注意事项。
Those are the basic elements I use to create an ANTICORRUPTION LAYER. There are a few other considerations.
• 通常,所设计的系统(您的子系统)将启动操作,如图14.8所示。但是,在某些情况下,另一个子系统可能需要向您的子系统请求某些内容或通知它某些事件。ANTICORRUPTION LAYER可以是双向的,在两个接口上定义具有自己的ADAPTERS的服务,可能使用具有对称转换的相同转换器。虽然实现ANTICORRUPTION LAYER通常不需要对另一个子系统进行任何更改,但为了让另一个系统调用ANTICORRUPTION LAYER的服务,可能需要进行更改。
• Typically, the system under design (your subsystem) will be initiating action, as implied by Figure 14.8. There are cases, however, when the other subsystem may need to request something of your subsystem or notify it of some event. An ANTICORRUPTION LAYER can be bidirectional, defining SERVICES on both interfaces with their own ADAPTERS, potentially using the same translators with symmetrical translations. Although implementing the ANTICORRUPTION LAYER doesn’t usually require any change to the other subsystem, it might be necessary in order to make the other system call on SERVICES of the ANTICORRUPTION LAYER.
图 14.8.防腐层的结构
Figure 14.8. The structure of an ANTICORRUPTION LAYER
• 通常需要某种通信机制来连接两个子系统,它们很可能位于不同的服务器上。在这种情况下,您必须决定将这些通信链接放在何处。如果您无法访问另一个子系统,则可能必须将链接放在 FACADE和另一个子系统之间。但是,如果 FACADE可以直接与其他子系统集成,那么一个不错的选择是将通信链接放在ADAPTER和FACADE之间,因为FACADE的协议可能比它所涵盖的内容更简单。还会出现整个ANTICORRUPTION LAYER可以与其他子系统共存的情况,在您的子系统和构成ANTICORRUPTION LAYER接口的服务之间放置通信链接或分发机制。这些是需要务实地做出的实施和部署决策。它们与ANTICORRUPTION LAYER的概念角色无关。
• You’ll usually need some communications mechanism to connect the two subsystems, and they could well be on separate servers. In this case, you have to decide where to place these communication links. If you have no access to the other subsystem, you may have to put the links between the FACADE and the other subsystem. However, if the FACADE can be integrated directly with the other subsystem, then a good option is to put the communication link between the ADAPTER and FACADE, because the protocol of the FACADE is presumably simpler than what it covers. There also will be cases where the entire ANTICORRUPTION LAYER can live with the other subsystem, placing communication links or distribution mechanisms between your subsystem and the SERVICES that make up the ANTICORRUPTION LAYER’s interface. These are implementation and deployment decisions to be made pragmatically. They have no bearing on the conceptual role of the ANTICORRUPTION LAYER.
• 如果您确实有权访问其他子系统,您可能会发现对其进行一些重构可以让您的工作更轻松。特别是,尝试为您将要使用的功能编写更明确的接口,如果可能的话,从自动化测试开始。
• If you do have access to the other subsystem, you may find that a little refactoring over there can make your job easier. In particular, try to write more explicit interfaces for the functionality you’ll be using, starting with automated tests, if possible.
• 当集成要求广泛时,翻译成本会大幅上升。可能需要在设计中的系统模型中做出选择,使其更接近外部系统,以便使翻译更容易。这样做非常小心谨慎,不会损害模型的完整性。只有当翻译难度失控时,才需要有选择地这样做。如果这种方法似乎是解决大部分重要问题的最自然方法,请考虑将子系统变成CONFORMIST模式,从而消除翻译。
• Where integration requirements are extensive, the cost of translation goes way up. It may be necessary to make choices in the model of the system under design that keep it closer to the external system, in order to make translation easier. Do this very carefully, without compromising the integrity of the model. It is only something to do selectively when translation difficulty gets out of hand. If this approach seems the most natural solution for much of the important part of the problem, consider making your subsystem a CONFORMIST pattern, eliminating translation.
• 如果其他子系统很简单或者具有清晰的界面,则可能不需要FACADE。
• If the other subsystem is simple or has a clean interface, you may not need the FACADE.
•如果功能特定于两个子系统之间的关系,则可以将其添加到ANTICORRUPTION LAYER中。我想到的两个有用功能是使用外部系统的审计跟踪或用于调试对其他接口的调用的跟踪逻辑。
• Functionality can be added to the ANTICORRUPTION LAYER if it is specific to the relationship of the two subsystems. An audit trail for use of the external system or trace logic for debugging the calls to the other interface are two useful features that come to mind.
请记住,ANTICORRUPTION LAYER是连接两个BOUNDED CONTEXTS的一种方式。通常,我们考虑的是其他人创建的系统;我们对系统的理解不完整,对它的控制很少。但这并不是唯一需要在子系统之间添加一点填充的情况。在某些情况下,如果两个自己设计的子系统基于不同的模型,使用 ANTICORRUPTION LAYER连接它们是有意义的。据推测,在这种情况下,您将完全控制双方,通常可以使用简单的转换层。但是,如果两个BOUNDED CONTEXTS已经分道扬镳但仍需要一些功能集成,则ANTICORRUPTION LAYER可以减少它们之间的摩擦。
Remember, an ANTICORRUPTION LAYER is a means of linking two BOUNDED CONTEXTS. Ordinarily, we are thinking of a system created by someone else; we have incomplete understanding of the system and little control over it. But that is not the only situation where you need a little padding between subsystems. There are even situations in which it makes sense to connect two subsystems of your own design with an ANTICORRUPTION LAYER, if they are based on different models. Presumably, in such a case, you will have full control over both sides and typically can use a simple translation layer. However, if two BOUNDED CONTEXTS have gone SEPARATE WAYS yet still have some need of functional integration, an ANTICORRUPTION LAYER can reduce the friction between them.
为了快速、小规模地发布第一个版本,我们将编写一个最小应用程序,该应用程序可以设置发货,然后通过翻译层将其传递给旧系统以进行预订和支持操作。由于我们专门构建了翻译层以保护我们的开发模型免受旧设计的影响,因此此翻译层是ANTICORRUPTION LAYER。
In order to have a small, quick first release, we will write a minimal application that can set up a shipment and then pass that to the legacy system through a translation layer for booking and support operations. Because we built the translation layer specifically to protect our developing model from the influence of the legacy design, this translation is an ANTICORRUPTION LAYER.
首先,ANTICORRUPTION LAYER将接受代表一批货物的对象,对其进行转换,将其传递给遗留系统并请求预订,然后获取确认并将其重新翻译成新设计的确认对象。这种隔离将使我们能够基本独立于旧应用程序开发新应用程序,尽管我们必须在翻译方面投入大量资金。
Initially, the ANTICORRUPTION LAYER will accept the objects representing a shipment, convert them, pass them to the legacy system and request a booking, and then capture the confirmation and translate it back into the confirmation object of the new design. This isolation will allow us to develop our new application mostly independently of the old one, though we’ll have to invest quite a bit in translation.
随着每次发布,新系统可以接管旧系统的更多功能,也可以简单地增加新价值而不取代现有功能,这取决于后续决策。这种灵活性以及在逐步过渡的同时持续运营组合系统的能力,可能值得花费大量资金来构建ANTICORRUPTION LAYER。
With each successive release, the new system can either take over more functions of the legacy or simply add new value without replacing existing capabilities, depending on later decisions. This flexibility, and the ability to continually operate the combined system while making a gradual transition, probably makes it worth the expense to build the ANTICORRUPTION LAYER.
为了保护边境不受邻近游牧武士部落的袭击,早期中国人修建了长城。长城并非坚不可摧的屏障,但它允许与邻国进行有节制的贸易,同时阻止入侵和其他不良影响。两千年来,长城划定了一条边界,帮助中国农业文明在较少受到外界混乱干扰的情况下确定自己的边界。
To protect their frontiers from raids by neighboring nomadic warrior tribes, the early Chinese built the Great Wall. It was not an impenetrable barrier, but it allowed a regulated commerce with neighbors while providing an impediment to invasion and other unwanted influence. For two thousand years it defined a boundary that helped the Chinese agricultural civilization to define itself with less disruption from the chaos outside.
尽管如果没有长城,中国也许不会形成如此独特的文化,但长城的修建成本极其高昂,至少使一个朝代破产,可能也是导致其灭亡的原因之一。孤立战略的好处必须与其成本相平衡。现在是时候采取务实态度,对模式进行适度修改,以便它能够更顺利地与外国模式相适应。
Although China might not have become so distinct a culture without the Great Wall, the Wall’s construction was immensely expensive and bankrupted at least one dynasty, probably contributing to its fall. The benefits of isolation strategies must be balanced against their costs. There is a time to be pragmatic and make measured revisions to the model, so that it can fit more smoothly with foreign ones.
任何集成都会产生开销,从单一有界上下文中的全面持续集成,到共享内核或客户/供应商开发团队的较小承诺,再到顺从者的片面性和反腐败层的防御姿态。集成可能非常有价值,但总是昂贵的。我们应该确保它确实是必要的。。。
There is overhead involved in any integration, from full-on CONTINUOUS INTEGRATION inside a single BOUNDED CONTEXT, through the lesser commitments of SHARED KERNELS or CUSTOMER/SUPPLIER DEVELOPER TEAMS, to the one-sidedness of the CONFORMIST and the defensive posture of the ANTICORRUPTION LAYER. Integration can be very valuable, but it is always expensive. We should be sure it is really needed. . . .
我们必须严格限定需求范围。两组没有不可或缺关系的功能可以相互切断。
We must ruthlessly scope requirements. Two sets of functionality with no indispensable relationship can be cut loose from each other.
集成总是昂贵的。有时效益却微乎其微。
Integration is always expensive. Sometimes the benefit is small.
除了协调团队通常需要的开支外,集成还迫使人们做出妥协。能够满足特定需求的简单专业模型必须让位于能够处理所有情况的更抽象的模型。也许一些完全不同的技术可以非常轻松地提供某些功能,但很难集成。也许有些团队很难相处,以至于当其他团队试图与他们合作时,一切都无法顺利进行。
In addition to the usual expense of coordinating teams, integration forces compromises. The simple specialized model that can satisfy a particular need must give way to the more abstract model that can handle all situations. Perhaps some completely different technology could provide certain features very easily, but it is difficult to integrate. Maybe some team is just so hard to get along with that nothing works very well when other teams try to collaborate with them.
在许多情况下,集成并没有带来显著的好处。如果两个功能部分不调用彼此的功能,或者不需要两者接触的对象之间的交互,或者在操作期间共享数据,那么即使通过转换层进行集成,也可能没有必要。仅仅因为功能在用例中相关并不意味着它们必须集成。
In many circumstances, integration provides no significant benefit. If two functional parts do not call upon each other’s functionality, or require interactions between objects that are touched by both, or share data during their operations, then integration, even through a translation layer, may not be necessary. Just because features are related in a use case does not mean they must be integrated.
所以:
Therefore:
声明一个BOUNDED CONTEXT与其他的没有任何联系,让开发人员在这个小的范围内找到简单、专门的解决方案。
Declare a BOUNDED CONTEXT to have no connection to the others at all, allowing developers to find simple, specialized solutions within this small scope.
这些功能仍然可以在中间件或 UI 层中组织,但不会共享逻辑,并且通过转换层进行的数据传输绝对最少 - 最好没有。
The features can still be organized in middleware or the UI layer, but there will be no sharing of logic, and an absolute minimum of data transfer through translation layers—preferably none.
一个项目团队着手开发新的保险索赔软件,将客户服务代理或理赔员需要的所有东西集成到一个系统中。经过一年的努力,团队成员陷入了困境。分析瘫痪和基础设施的大量前期投资让他们一无所获,管理层也越来越不耐烦。更严重的是,他们试图做的事情的范围让他们不堪重负。
One project team had set out to develop new software for insurance claims that would integrate into one system everything a customer service agent or a claims adjuster needed. After a year of effort, team members were stuck. A combination of analysis paralysis and a major up-front investment in infrastructure had found them with nothing to show an increasingly impatient management. More seriously, the scope of what they were trying to do was overwhelming them.
一位新项目经理强迫所有人聚在一个房间里,用一周时间制定新计划。首先,他们列出需求清单,并试图估计它们的难度并确定其重要性。他们毫不留情地剔除那些困难和不重要的需求。然后他们开始整理剩下的清单。那一周,他们在那个房间里做出了许多明智的决定,但最终只有一个决定是重要的。在某个时候,人们意识到有些功能的集成几乎没有什么附加价值。例如,理赔员需要访问一些现有的数据库,而他们目前的访问非常不方便。但是,尽管用户需要这些数据,但拟议的软件系统的其他功能都不会使用这些数据。
A new project manager forced everyone into a room for a week to form a new plan. First they made lists of requirements and tried to estimate their difficulty and assign importance. They ruthlessly chopped the difficult and unimportant ones. Then they started to bring order to the remaining list. Many smart decisions were made in that room that week, but in the end, only one turned out to be important. At some point it was recognized that there were some features for which integration provided little added value. For example, adjusters needed access to some existing databases, and their current access was very inconvenient. But, although the users needed to have this data, none of the other features of the proposed software system would use it.
团队成员提出了各种提供便捷访问的方法。在一种情况下,可以将关键报告导出为 HTML 并放在内联网上。在另一种情况下,可以向理赔员提供使用标准软件包编写的专门查询。所有这些功能都可以通过在内联网页面上组织链接或在用户桌面上放置按钮来集成。
Team members proposed various ways of providing easy access. In one case, a key report could be exported as HTML and placed on the intranet. In another case, adjusters could be provided with a specialized query written using a standard software package. All these functions could be integrated by organizing links on an intranet page or by placing buttons on the user’s desktop.
该团队启动了一系列小型项目,这些项目除了尝试从同一菜单启动之外,没有尝试进行更多集成。几项有价值的功能几乎在一夜之间就交付了。去掉这些无关紧要的功能后,只剩下一组精简的需求,这些需求似乎一度给主要应用程序的交付带来了希望。
The team launched a set of small projects that attempted no more integration than launching from the same menu. Several valuable capabilities were delivered almost overnight. Dropping the baggage of these extraneous features left a distilled set of requirements that seemed for a while to give hope for delivery of the main application.
本来可以这样,但不幸的是,团队又回到了旧习惯。他们再次陷入瘫痪。最后,他们唯一的遗产就是那些分道扬镳的小应用程序。
It could have gone that way, but unfortunately the team slipped back into old habits. They paralyzed themselves again. In the end, their only legacy turned out to be those small applications that had gone their SEPARATE WAYS.
采取不同的方式会排除一些选择。尽管持续重构最终可以撤销任何决定,但很难合并完全孤立地开发的模型。如果最终需要集成,则翻译层将是必要的,并且可能很复杂。当然,无论如何你都会面临这种情况。
Taking SEPARATE WAYS forecloses some options. Although continuous refactoring can eventually undo any decision, it is hard to merge models that have developed in complete isolation. If integration turns out to be needed after all, translation layers will be necessary and may be complex. Of course, this is something you will face anyway.
现在,让我们回过头来讨论更具合作关系的问题,看看如何扩大一体化。……
Now, turning back to more cooperative relationships, let’s look at ways to scale up integration. . . .
通常,对于每个有界上下文,您将为必须集成的上下文之外的每个组件定义一个转换层。当集成是一次性的时,这种为每个外部系统插入转换层的方法可以以最低的成本避免模型损坏。但是当您发现子系统需求量很大时,您可能需要一种更灵活的方法。
Typically for each BOUNDED CONTEXT, you will define a translation layer for each component outside the CONTEXT with which you have to integrate. Where integration is one-off, this approach of inserting a translation layer for each external system avoids corruption of the models with a minimum of cost. But when you find your subsystem in high demand, you may need a more flexible approach.
当一个子系统必须与许多其他子系统集成时,为每个子系统定制一个翻译器可能会让团队陷入困境。需要维护的东西越来越多,而且在进行更改时需要担心的事情也越来越多。
When a subsystem has to be integrated with many others, customizing a translator for each can bog down the team. There is more and more to maintain, and more and more to worry about when changes are made.
团队可能一遍又一遍地做同样的事情。如果子系统具有一致性,那么很可能可以将其描述为一组服务,涵盖其他子系统的共同需求。
The team may be doing the same thing again and again. If there is any coherence to the subsystem, it is probably possible to describe it as a set of SERVICES that cover the common needs of other subsystems.
设计一个足够清晰、能够被多个团队理解和使用的协议要困难得多,因此只有当子系统的资源可以被描述为一组有凝聚力的服务并且存在大量集成时,它才会有回报。在这种情况下,它可以在维护模式和持续开发之间产生差异。
It is a lot harder to design a protocol clean enough to be understood and used by multiple teams, so it pays off only when the subsystem’s resources can be described as a cohesive set of SERVICES and when there are a significant number of integrations. Under those circumstances, it can make the difference between maintenance mode and continuing development.
所以:
Therefore:
定义一个协议,以一组服务的形式提供对您的子系统的访问。开放该协议,以便所有需要与您集成的人都可以使用它。增强和扩展该协议以处理新的集成要求,除非单个团队有特殊需求。然后,使用一次性转换器来增强该特殊情况的协议,以便共享协议可以保持简单和连贯。
Define a protocol that gives access to your subsystem as a set of SERVICES. Open the protocol so that all who need to integrate with you can use it. Enhance and expand the protocol to handle new integration requirements, except when a single team has idiosyncratic needs. Then, use a one-off translator to augment the protocol for that special case so that the shared protocol can stay simple and coherent.
这种形式化的沟通意味着一些共享的模型词汇——SERVICE接口的基础。因此,其他子系统与OPEN HOST的模型耦合,其他团队被迫学习 HOST 团队使用的特定方言。在某些情况下,使用众所周知的PUBLISHED LANGUAGE作为交换模型可以减少耦合并简化理解。...
This formalization of communication implies some shared model vocabulary—the basis of the SERVICE interfaces. As a result, the other subsystems become coupled to the model of the OPEN HOST, and other teams are forced to learn the particular dialect used by the HOST team. In some situations, using a well-known PUBLISHED LANGUAGE as the interchange model can reduce coupling and ease understanding. . . .
两个BOUNDED CONTEXTS模型之间的翻译需要一种共同的语言。
The translation between the models of two BOUNDED CONTEXTS requires a common language.
当两个领域模型必须共存,并且必须在它们之间传递信息时,转换过程本身就会变得复杂,难以记录和理解。如果我们要构建一个新系统,我们通常会认为我们的新模型是最好的,所以我们会考虑直接转换成新模型。但有时我们会增强一组旧系统并尝试将它们集成。选择一个混乱的模型而不是另一个可能是两害相权取其轻。
When two domain models must coexist and information must pass between them, the translation process itself can become complex and hard to document and understand. If we are building a new system, we will typically believe that our new model is the best available, and so we will think in terms of translating directly into it. But sometimes we are enhancing a set of older systems and trying to integrate them. Choosing one messy model over the other may be choosing the lesser of two evils.
另一种情况:当企业想要相互交换信息时,他们该怎么做?期望一方采用另一方的领域模型不仅不现实,而且对双方来说可能都不是理想的选择。领域模型是为了解决用户的问题而开发的;这样的模型可能包含不必要的功能,使与另一个系统的通信变得复杂。此外,如果将其中一个应用程序的基础模型用作通信媒介,则不能随意更改它以满足新的需求,而必须非常稳定才能支持持续的通信角色。
Another situation: When businesses want to exchange information with one another, how do they do it? Not only is it unrealistic to expect one to adopt the domain model of the other, it may be undesirable for both parties. A domain model is developed to solve problems for its users; such a model may contain features that needlessly complicate communication with another system. Also, if the model underlying one of the applications is used as the communications medium, it cannot be changed freely to meet new needs, but must be very stable to support the ongoing communication role.
直接与现有领域模型进行转换可能不是一个好的解决方案。这些模型可能过于复杂或分解得不好。它们可能没有文档记录。如果将一种语言用作数据交换语言,它基本上会变得僵化,无法响应新的开发需求。
Direct translation to and from the existing domain models may not be a good solution. Those models may be overly complex or poorly factored. They are probably undocumented. If one is used as a data interchange language, it essentially becomes frozen and cannot respond to new development needs.
OPEN HOST SERVICE使用标准化协议进行多方集成。它采用域模型进行系统间交换,即使这些系统内部可能不使用该模型。在这里,我们更进一步,发布该语言,或者找到一种已经发布的语言。我所说的发布只是意味着该语言可供可能有兴趣使用它的社区随时使用,并且有足够的文档记录以允许独立解释兼容。
The OPEN HOST SERVICE uses a standardized protocol for multiparty integration. It employs a model of the domain for interchange between systems, even though that model may not be used internally by those systems. Here we go a step further and publish that language, or find one that is already published. By publish I simply mean that the language is readily available to the community that might be interested in using it, and is sufficiently documented to allow independent interpretations to be compatible.
最近,电子商务界对一项新技术感到非常兴奋:可扩展标记语言 (XML) 有望使数据交换更加容易。XML 的一个非常有价值的特性是,通过文档类型定义 (DTD) 或 XML 模式,XML 允许正式定义一种可以将数据翻译成的专门领域语言。行业组织已经开始成立,目的是为其行业定义一个单一的标准 DTD,以便多方之间可以交流化学公式信息或遗传编码。本质上,这些组织正在以语言定义的形式创建共享领域模型。
Recently, the world of e-commerce has become very excited about a new technology: Extensible Markup Language (XML) promises to make interchange of data much easier. A very valuable feature of XML is that, through the document type definition (DTD) or through XML schemas, XML allows the formal definition of a specialized domain language into which data can be translated. Industry groups have begun to form for the purpose of defining a single standard DTD for their industry so that, say, chemical formula information or genetic coding can be communicated between many parties. Essentially these groups are creating a shared domain model in the form of a language definition.
所以:
Therefore:
使用一种有据可查的共享语言来表达必要的领域信息,作为一种通用的交流媒介,并根据需要将该语言翻译成或转译成该语言。
Use a well-documented shared language that can express the necessary domain information as a common medium of communication, translating as necessary into and out of that language.
语言不必从头开始创建。许多年前,我与一家公司签约,该公司的一款软件产品用 Smalltalk 编写,使用 DB2 存储数据。该公司希望能够灵活地将软件分发给没有 DB2 许可证的用户,并与我签约构建一个 Btrieve 接口,Btrieve 是一种轻量级数据库引擎,具有免费的运行时分发许可证。Btrieve 不是完全关系型的,但我的客户只使用了 DB2 的一小部分功能,并且是这两个数据库的最小公分母。该公司的开发人员在 DB2 之上构建了一些关于对象存储的抽象。我决定将这项工作用作我的 Btrieve 组件的接口。
The language doesn’t have to be created from scratch. Many years ago, I was contracted by a company that had a software product written in Smalltalk that used DB2 to store its data. The company wanted the flexibility to distribute the software to users without a DB2 license and contracted me to build an interface to Btrieve, a lighter-weight database engine that had a free runtime distribution license. Btrieve is not fully relational, but my client was using only a small part of DB2’s power and was within the lowest common denominator of the two databases. The company’s developers had built on top of DB2 some abstractions that were in terms of the storage of objects. I decided to use this work as the interface for my Btrieve component.
这种方法确实有效。该软件顺利地与我客户的系统集成。但是,由于客户的设计中缺乏对持久对象抽象的正式规范或文档,因此我需要做大量工作才能弄清楚新组件的要求。此外,没有太多机会重用该组件将其他应用程序从 DB2 迁移到 Btrieve。而且新软件更加根深蒂固地巩固了公司的持久性模型,因此重构该持久对象模型将更加困难。
This approach did work. The software smoothly integrated with my client’s system. However, the lack of a formal specification or documentation of the abstractions of persistent objects in the client’s design meant a lot of work for me to figure out the requirements of the new component. Also, there wasn’t much opportunity to reuse the component to migrate some other application from DB2 to Btrieve. And the new software more deeply entrenched the company’s model of persistence, so that refactoring that model of persistent objects would have been even more difficult.
更好的方法可能是识别公司正在使用的 DB2 接口的子集,然后支持它。DB2 的接口由 SQL 和许多专有协议组成。尽管它非常复杂,但接口规范严格,文档齐全。复杂性本来应该是由于只使用了一小部分接口,因此问题得到了缓解。如果开发了一个组件来模拟 DB2 接口的必要子集,那么只需识别该子集,就可以非常有效地为开发人员提供文档。集成到该组件的应用程序已经知道如何与 DB2 通信,因此几乎不需要做任何额外的工作。未来对持久层的重新设计将仅限于使用 DB2 子集,就像增强之前一样。
A better way might have been to identify the subset of the DB2 interface that the company was using and then support that. The interface of DB2 is made up of SQL and a number of proprietary protocols. Although it is very complex, the interface is tightly specified and thoroughly documented. The complexity would have been mitigated because only a small subset of the interface was being used. If a component had been developed that emulated the necessary subset of the DB2 interface, it could have been very effectively documented for developers simply by identifying the subset. The application it was integrated into already knew how to talk to DB2, so little additional work would have been needed. Future redesign of the persistence layer would have been constrained only to the use of the DB2 subset, just as before the enhancement.
DB2 接口是PUBLISHED LANGUAGE的一个示例。在这种情况下,两个模型不属于业务领域,但所有原则都同样适用。由于协作中的其中一个模型已经是PUBLISHED LANGUAGE,因此无需引入第三种语言。
The DB2 interface is an example of a PUBLISHED LANGUAGE. In this case, the two models are not in the business domain, but all the principles apply just the same. Because one of the models in the collaboration is already a PUBLISHED LANGUAGE, there is no need to introduce a third language.
工业界和学术界使用无数程序来编目、分析和操作化学式。交换数据一直很困难,因为几乎每个程序都使用不同的领域模型来表示化学结构。当然,大多数程序都是用 FORTRAN 等语言编写的,这些语言无论如何都不能完全表达领域模型。每当有人想共享数据时,他们都必须解开其他系统数据库的细节并制定某种翻译方案。
Innumerable programs are used to catalog, analyze, and manipulate chemical formulas in industry and academia. Exchanging data has always been difficult, because almost every program uses a different domain model to represent chemical structures. And of course, most of them are written in languages, such as FORTRAN, that do not express the domain model very fully anyway. Whenever anyone wanted to share data, they had to unravel the details of the other system’s database and work out some sort of translation scheme.
化学标记语言 (CML) 是 XML 的一种方言,旨在作为该领域的通用交换语言,由代表学术界和工业界的团体开发和管理(Murray-Rust 等人,1995 年)。
Enter the Chemical Markup Language (CML), a dialect of XML intended as a common interchange language for this domain, developed and managed by a group representing academics and industry (Murray-Rust et al. 1995).
化学信息非常复杂多样,而且随着新发现而不断变化。因此,他们开发了一种语言来描述基本信息,例如有机和无机分子的化学式、蛋白质序列、光谱或物理量。
Chemical information is very complex and diverse, and it changes all the time with new discoveries. So they developed a language that could describe the basics, such as the chemical formulas of organic and inorganic molecules, protein sequences, spectra, or physical quantities.
现在该语言已经发布,可以开发以前根本不值得费力编写的工具,因为这些工具只能用于一个数据库。例如,开发了一个名为 JUMBO Browser 的 Java 应用程序,它可以创建存储在 CML 中的化学结构的图形视图。因此,如果您将数据放入 CML 格式,您就可以访问此类可视化工具。
Now that the language has been published, tools can be developed that would never have been worth the trouble to write before, when they would have only been usable for one database. For example, a Java application, called the JUMBO Browser, was developed that creates graphical views of chemical structures stored in CML. So if you put your data in the CML format, you’ll have access to such visualization tools.
事实上,CML 通过使用 XML(一种“已发布的元语言”)获得了双重优势。由于人们熟悉 XML,CML 的学习曲线变得平坦;各种现成的工具(如解析器)使实施变得容易;而关于处理 XML 的各个方面的许多书籍也为文档提供了帮助。
In fact, CML gained a double advantage by using XML, a sort of “published meta-language.” The learning curve of CML is flattened by people’s familiarity with XML; the implementation is eased by various off-the-shelf tools, such as parsers; and documentation is helped by the many books written on all aspects of handling XML.
这是 CML 的一个小例子。对于像我这样的非专业人士来说,它只能模糊地理解,但其原理是清楚的。
Here is a tiny sample of CML. It is only vaguely intelligible to nonspecialists like myself, but the principle is clear.
<CML.ARR ID="array3" EL.TYPE=FLOAT NAME="原子轨道电子布居" SIZE=30 GLO.ENT=CML.THE.AOEPOPS>
1.17947 0.95091 0.97175 1.00000 1.17947 0.95090 0.97174 1.00000 1.17946
0.98215 0.94049 1.00000 1.17946 0.95091 0.97174
1.00000 1.17946 0.95091 0.97174 1.00000 1.17946 0.98215 0.94049 1.00000
0.89789 0.89790 0.89789 0.89789 0.89790
0.89788
<CML.ARR ID="array3" EL.TYPE=FLOAT NAME="ATOMIC ORBITAL ELECTRON POPULATIONS" SIZE=30 GLO.ENT=CML.THE.AOEPOPS>
1.17947 0.95091 0.97175 1.00000 1.17947 0.95090 0.97174 1.00000
1.17946 0.98215 0.94049 1.00000 1.17946 0.95091 0.97174 1.00000
1.17946 0.95091 0.97174 1.00000 1.17946 0.98215 0.94049 1.00000
0.89789 0.89790 0.89789 0.89789 0.89790 0.89788
</CML.ARR>
有六个印度斯坦人,
他们好学好问,
去看大象
(虽然他们都是瞎子),
希望通过观察,每个人
都能得到满足。第一
个人走近大象,不巧摔倒在大象宽阔而结实的侧面,立刻开始大叫:“上帝保佑我!但是大象很像一堵墙!” ……第三个人走近大象,碰巧手里抓住了大象扭动的鼻子,于是大胆地站起来说:“我看,”他说,“大象很像一条蛇。”第四个人伸出急切的手,摸了摸大象的膝盖。“这只奇妙的野兽最像什么,这是非常明显的,”他说,“很明显,大象很像一棵树!” ……第六个人刚开始在大象周围摸索,就抓住了落在他视线内的摆动的尾巴,他说:“我看见了,大象很像一根绳子!”就这样,这些印度斯坦人大声争论了很久,每个人都坚持自己的观点,非常固执,虽然他们各自都有部分正确,但所有人都错了! ……
It was six men of Indostan
To learning much inclined,
Who went to see the Elephant
(Though all of them were blind),
That each by observation
Might satisfy his mind.
The First approached the Elephant,
And happening to fall
Against his broad and sturdy side,
At once began to bawl:
"God bless me! but the Elephant
Is very like a wall!"
. . .
The Third approached the animal,
And happening to take
The squirming trunk within his hands,
Thus boldly up and spake:
"I see," quoth he, "the Elephant
Is very like a snake."
The Fourth reached out his eager hand,
And felt about the knee.
"What most this wondrous beast is like
Is mighty plain," quoth he;
"’Tis clear enough the Elephant
Is very like a tree!"
. . .
The Sixth no sooner had begun
About the beast to grope,
Than, seizing on the swinging tail
That fell within his scope,
"I see," quoth he, "the Elephant
Is very like a rope!"
And so these men of Indostan
Disputed loud and long,
Each in his own opinion
Exceeding stiff and strong,
Though each was partly in the right,
And all were in the wrong!
. . .
—摘自约翰·戈弗雷·萨克斯(1816-1887 年)的《盲人和大象》,该书基于印度教经典《乌达那》中的一个故事
—From “The Blind Men and the Elephant,” by John Godfrey Saxe (1816–1887), based on a story in the Udana, a Hindu text
根据他们与大象互动的目标,即使盲人对大象的性质没有完全达成一致,他们仍然可能取得进展。如果不需要整合,那么模型不统一也没关系。如果他们需要某种整合,他们可能实际上不必就大象是什么达成一致,但只要认识到他们不同意,他们就会获得很多价值。这样,至少他们不会在不知不觉中互相矛盾。
Depending on their goals in interacting with the elephant, the various blind men may still be able to make progress, even if they don’t fully agree on the nature of the elephant. If no integration is required, then it doesn’t matter that the models are not unified. If they require some integration, they may not actually have to agree on what an elephant is, but they will get a lot of value from merely recognizing that they don’t agree. This way, at least they don’t unknowingly talk at cross-purposes.
图 14.9中的图表是盲人为大象建立的模型的 UML 表示。建立单独的有界上下文后,情况就足够清晰了,他们可以想出一种方法来相互沟通他们共同关心的几个方面:也许是大象的位置。
The diagrams in Figure 14.9 are UML representations of the models the blind men have formed of the elephant. Having established separate BOUNDED CONTEXTS, the situation is clear enough for them to work out a way to communicate with each other about the few aspects they care about in common: the location of the elephant, perhaps.
图 14.9. 四种情况:无整合
Figure 14.9. Four contexts: no integration
图 14.10. 四种情境:最低限度的整合
Figure 14.10. Four contexts: minimal integration
随着盲人想要分享更多关于大象的信息,共享单个有界上下文的价值就会上升。但统一不同的模型是一项挑战。没有人会放弃自己的模型而采用其他模型。毕竟,摸到尾巴的人知道大象不像树,那个模型对他来说毫无意义和用处。统一多个模型几乎总是意味着创建一个新模型。
As the blind men want to share more information about the elephant, the value of sharing a single BOUNDED CONTEXT goes up. But unifying the disparate models is a challenge. None of them is likely to give up his model and adopt one of the others. After all, the man who touched the tail knows the elephant is not like a tree, and that model would be meaningless and useless to him. Unifying multiple models almost always means creating a new model.
通过一些想象力和持续的讨论(可能很激烈),盲人最终会意识到他们已经一直在描述和建模一个更大整体的不同部分。对于许多目的而言,部分-整体统一可能不需要太多额外的工作。至少整合的第一阶段只需要弄清楚各部分是如何关联的。对于某些需要来说,将大象视为一堵墙可能就足够了,墙由树干支撑,一端有绳子,另一端有蛇。
With some imagination and continued discussion (probably heated), the blind men could eventually recognize that they have been describing and modeling different parts of a larger whole. For many purposes, a part-whole unification may not require much additional work. At least the first stage of integration only requires figuring out how the parts are related. It may be adequate for some needs to view an elephant as a wall, held up by tree trunks, with a rope at one end and a snake at the other.
图 14.11. 一个背景:粗略整合
Figure 14.11. One context: crude integration
各种大象模型的统一比大多数此类合并更容易。不幸的是,当两个模型纯粹描述整体的不同部分时,情况就不同了,尽管这通常是差异的一个方面。当两个模型以不同的方式看待同一部分时,事情会更加困难。如果两个人触摸了象鼻,一个人把它描述成一条蛇,另一个人把它描述成一根消防水管,他们会遇到更大的困难。他们都无法接受对方的模型,因为它与他自己的经验相矛盾。事实上,他们需要一种新的抽象概念,将蛇的“生命力”与消防水管的喷水功能结合起来,但这种抽象概念需要省去第一个模型的不恰当含义,比如对可能有毒牙的预期,或者可以从身体上分离并卷入消防车隔间的能力。
The unification of the various elephant models is easier than most such mergers. Unfortunately, it is the exception when two models purely describe different parts of the whole, although this is often one aspect of the difference. Matters are more difficult when two models are looking at the same part in a different way. If two men had touched the trunk and one described it as a snake and the other described it as a fire hose, they would have had more difficulty. Neither can accept the other’s model, because it contradicts his own experience. In fact, they need a new abstraction that incorporates the “aliveness” of a snake with the water-shooting functionality of a fire hose, but one that leaves out the inapt implications of the first models, such as the expectation of possibly venomous fangs, or the ability to be detached from the body and rolled up into a compartment in a fire truck.
尽管我们已经将各个部分组合成一个整体,但最终的模型还是很粗糙。它不连贯,缺乏遵循底层领域轮廓的感觉。在不断完善的过程中,新的见解可能会带来更深层次的模型。新的应用需求也可能迫使我们转向更深层次的模型。如果大象开始移动,“树”理论就过时了,我们的盲目建模者可能会突破“腿”的概念。
Even though we have combined the parts into a whole, the resulting model is crude. It is incoherent, lacking any sense of following contours of an underlying domain. New insights could lead to a deeper model in a process of continuous refinement. New application requirements can also force the move to a deeper model. If the elephant starts moving, the “tree” theory is out, and our blind modelers may break through to the concept of “legs.”
图 14.12. 一个背景:更深的模型
Figure 14.12. One context: deeper model
模型整合的第二阶段往往会去除个别模型中偶然或不正确的部分,并创建新的概念——在本例中,“动物”由“鼻子”、“腿”、“身体”和“尾巴”组成——每个部分都有自己的属性,并与其他部分有明确的关系。成功的模型统一在很大程度上取决于极简主义。大象的鼻子比蛇既多又少,但“少”可能比“多”更重要。与其拥有错误的毒牙特征,不如没有喷水的能力。
This second pass of model integration tends to slough off incidental or incorrect aspects of the individual models and creates new concepts—in this case, “animal” with parts “trunk,” “leg,” “body,” and “tail”—each of which has its own properties and clear relationships to other parts. Successful model unification, to a large extent, hinges on minimalism. An elephant trunk is both more and less than a snake, but the “less” is probably more important than the “more.” Better to lack the water-spewing ability than to have an incorrect poison-fang feature.
如果目标只是找到大象,那么在每个模型的位置表达之间进行转换就可以了。当需要更多集成时,统一模型不必在第一版就完全成熟。对于某些需求来说,将大象视为一堵墙可能就足够了,墙由树干支撑,一端有绳子,另一端有蛇。后来,在新的需求以及理解和沟通的推动下,模型可以得到深化和细化。
If the goal is simply to find the elephant, then translating between each model’s expression of location will do. When more integration is needed, the unified model doesn’t have to reach full maturity in the first version. It may be adequate for some needs to view an elephant as a wall, held up by tree trunks, with a rope at one end and a snake at the other. Later, driven by new requirements and by improved understanding and communication, the model can be deepened and refined.
识别多个相互冲突的领域模型实际上就是面对现实。通过明确定义每个模型适用的上下文,您可以保持每个模型的完整性,并清楚地看到您想要在两者之间创建的任何特定接口的含义。盲人无法看到整个大象,但只要他们认识到自己感知的不完整性,他们的问题就可以解决。
Recognizing multiple, clashing domain models is really just facing reality. By explicitly defining a context within which each model applies, you can maintain the integrity of each and clearly see the implications of any particular interface you want to create between the two. There is no way for the blind men to see the whole elephant, but their problem would be manageable if only they recognized the incompleteness of their perception.
绘制背景地图以反映任何给定时间的当前情况非常重要。但是,一旦完成,您很可能想要改变这一现实。现在您可以开始有意识地选择背景边界和关系。以下是一些指导原则。
It is important always to draw the CONTEXT MAP to reflect the current situation at any given time. Once that’s done, though, you may very well want to change that reality. Now you can begin to consciously choose CONTEXT boundaries and relationships. Here are some guidelines.
首先,团队必须决定在何处定义有界上下文以及它们之间应有何种关系。团队必须做出这些决定,或者至少这些决定必须传达给整个团队并让每个人都理解。事实上,这样的决定往往涉及超出您自己团队的协议。就其优点而言,关于是否扩展或划分有界上下文的决定应基于独立团队行动的价值与直接和丰富集成的价值之间的成本效益权衡。在实践中,团队之间的政治关系通常决定了系统的集成方式。由于报告结构,技术上有利的统一可能是不可能的。管理层可能会要求进行难以处理的合并。您不会总是得到您想要的东西,但至少您可以评估和传达所产生的部分成本,并采取措施减轻它。从现实的上下文图开始,务实地选择转换。
First, teams have to make decisions about where to define BOUNDED CONTEXTS and what sort of relationships to have between them. Teams have to make these decisions, or at least the decisions have to be propagated to the entire team and understood by everyone. In fact, such decisions often involve agreements beyond your own team. On the merits, decisions about whether to expand or to partition BOUNDED CONTEXTS should be based on the cost-benefit trade-off between the value of independent team action and the value of direct and rich integration. In practice, political relationships between teams often determine how systems are integrated. A technically advantageous unification may be impossible because of reporting structure. Management may dictate an unwieldy merger. You won’t always get what you want, but at least you may be able to assess and communicate something of the cost incurred, and take steps to mitigate it. Start with a realistic CONTEXT MAP and be pragmatic in choosing transformations.
当我们在进行软件项目时,我们主要关注的是团队正在更改的系统部分(“设计中的系统”),其次是它将与之通信的系统。在典型情况下,设计中的系统将被划分为主要开发团队将要处理的一两个有界上下文,也许还有一两个上下文充当辅助角色。除此之外,还有这些上下文与外部系统之间的关系。这是一个简单而典型的视图,可以粗略地预测您可能遇到的情况。
When we are working on a software project, we are interested primarily in the parts of the system our team is changing (the “system under design”) and secondarily in the systems it will communicate with. In a typical case, the system under design is going to get carved into one or two BOUNDED CONTEXTS that the main development teams will be working on, perhaps with another CONTEXT or two in a supporting role. In addition to that are the relationships between these CONTEXTS and the external systems. This is a simple, typical view, to give some rough expectation for what you are likely to encounter.
我们确实是我们正在工作的那个主要背景的一部分,这必然会反映在我们的背景地图中。如果我们意识到偏见,并注意何时超越该地图适用性的界限,这不是问题。
We really are part of that primary CONTEXT we are working in, and that is bound to be reflected in our CONTEXT MAP. This isn’t a problem if we are aware of the bias and are mindful of when we step outside the limits of that MAP’s applicability.
有无数种情况和无数种选择来划定有界上下文的边界。但是通常来说,斗争是为了平衡以下一些力量:
There are an unlimited variety of situations and an unlimited number of options for drawing the boundaries of BOUNDED CONTEXTS. But typically the struggle is to balance some subset of the following forces:
青睐更大范围的有界上下文
Favoring Larger BOUNDED CONTEXTS
• 当使用统一模型处理更多任务时,用户任务之间的流程会更加顺畅。
• Flow between user tasks is smoother when more is handled with a unified model.
• 理解一个连贯的模型比理解两个不同的模型加上映射要容易得多。
• It is easier to understand one coherent model than two distinct ones plus mappings.
• 两个模型之间的转换可能很困难(有时甚至是不可能的)。
• Translation between two models can be difficult (sometimes impossible).
• 共享语言促进清晰的团队沟通。
• Shared language fosters clear team communication.
青睐更小的 B OUNDED C ONTEXTS
Favoring Smaller BOUNDED CONTEXTS
• 减少了开发人员之间的沟通开销。
• Communication overhead between developers is reduced.
•对于较小的团队和代码库来说,持续集成更加容易。
• CONTINUOUS INTEGRATION is easier with smaller teams and code bases.
• 更大的环境可能需要更多功能的抽象模型,而这需要短缺的技能。
• Larger contexts may call for more versatile abstract models, requiring skills that are in short supply.
• 不同的模型可以满足特殊需求或涵盖专业用户群体的术语以及无处不在的语言的专业方言。
• Different models can cater to special needs or encompass the jargon of specialized groups of users, along with specialized dialects of the UBIQUITOUS LANGUAGE.
不同BOUNDED CONTEXTS之间的功能深度集成是不切实际的。集成仅限于一个模型中可以用另一个模型严格表述的部分,而且即使是这种级别的集成也可能需要付出相当大的努力。当两个系统之间有一个小接口时,这是有意义的。
Deep integration of functionality between different BOUNDED CONTEXTS is impractical. Integration is limited to those parts of one model that can be rigorously stated in terms of the other model, and even this level of integration may take considerable effort. This makes sense when there will be a small interface between two systems.
最好从最简单的决策开始。有些子系统显然不属于正在开发的系统的任何有界上下文。例如,您不会立即替换的主要遗留系统和提供您将要替换的服务的外部系统需要。您可以立即识别这些并准备将它们从您的设计中分离出来。
It is best to start with the easiest decisions. Some subsystems will clearly not be in any BOUNDED CONTEXT of the system under development. Examples would be major legacy systems that you are not immediately replacing and external systems that provide services you’ll need. You can identify these immediately and prepare to segregate them from your design.
在这里我们必须谨慎对待我们的假设。可以方便地将每个系统视为构成其自己的有界上下文,但是大多数外部系统都只是勉强符合定义。首先,有界上下文是通过在特定边界内统一模型的意图来定义的。您可能可以控制遗留系统的维护,在这种情况下您可以声明该意图,或者遗留团队可能协调良好并正在执行非正式形式的持续集成,但不要想当然。检查它,如果开发没有很好地集成,要特别小心。在这些系统的不同部分发现语义矛盾并不罕见。
Here we must be careful about our assumptions. It is convenient to think of each of these systems as constituting its own BOUNDED CONTEXT, but most external systems only weakly meet the definition. First, a BOUNDED CONTEXT is defined by an intention to unify the model within certain boundaries. You may have control of maintenance of the legacy system, in which case you can declare the intention, or the legacy team may be well coordinated and be carrying out an informal form of CONTINUOUS INTEGRATION, but don’t take it for granted. Check into it, and if the development is not well integrated, be particularly cautious. It is not unusual to find semantic contradictions in different parts of such systems.
这里有三种模式可以应用。首先,考虑不同的方式。是的,如果不需要集成,你就不会包括它们。但要非常确定。让用户轻松访问两个系统是否足够?集成既昂贵又分散注意力,所以尽可能减轻你的项目负担。
There are three patterns that can apply here. First, to consider SEPARATE WAYS. Yes, you wouldn’t have included them if you didn’t need integration. But be really sure. Would it be sufficient to give the user easy access to both systems? Integration is expensive and distracting, so unburden your project as much as you can.
如果集成确实必不可少,那么您可以在两个极端之间进行选择:顺从者或反腐败层。做一个顺从者并不好玩。您的创造力和新功能选项将受到限制。在构建一个重要的新系统时,坚持遗留系统或外部系统的模型不太可能切合实际(毕竟,您为什么要构建新系统?)。但是,在对将继续成为主导系统的大型系统的外围扩展的情况下,坚持使用遗留模型可能是合适的。这种选择的示例包括通常用 Excel 或其他简单工具编写的轻量级决策支持工具。如果您的应用程序确实是对现有系统的扩展,并且您与该系统的接口将会很大,则上下文之间的转换很容易比应用程序功能本身更大。并且,即使您已将自己置于另一个系统的BOUNDED CONTEXT中,仍然有一些空间可以进行良好的设计工作。如果另一个系统背后有一个可辨别的领域模型,你可以通过使该模型比旧系统更明确来改进你的实现,只要你严格遵循旧模型。如果你决定采用CONFORMIST设计,你必须全心全意地去做。你只能限制自己进行扩展,而不会对现有模型进行任何修改。
If the integration is really essential, you can choose between two extremes: CONFORMIST or ANTICORRUPTION LAYER. It is not fun to be a CONFORMIST. Your creativity and your options for new functionality will be limited. In building a major new system, it is unlikely to be practical to adhere to the model of a legacy or external system (after all, why are you building a new system?). However, sticking with the legacy model may be appropriate in the case of peripheral extensions to a large system that will continue to be the dominant system. Examples of this choice include the lightweight decision-support tools that are often written in Excel or other simple tools. If your application is really an extension to the existing system and your interface with that system is going to be large, the translation between CONTEXTS can easily be a bigger job than the application functionality itself. And there is still some room for good design work, even though you have placed yourself in the BOUNDED CONTEXT of the other system. If there is a discernable domain model behind the other system, you can improve your implementation by making that model more explicit than it was in the old system, just as long as you strictly conform to the old model. If you decide on a CONFORMIST design, you must do it wholeheartedly. You restrict yourself to extension only, with no modification of the existing model.
当设计的系统的功能比现有系统的扩展更复杂时,当您与另一个系统的接口很小,或者另一个系统设计得非常糟糕时,您确实需要自己的BOUNDED CONTEXT,这意味着构建一个转换层,甚至是一个ANTICORRUPTION LAYER。
When the functionality of the system under design is going to be more involved than an extension to an existing system, where your interface to the other system is small, or where the other system is very badly designed, you’ll really want your own BOUNDED CONTEXT, which means building a translation layer, or even an ANTICORRUPTION LAYER.
您的项目团队实际构建的软件是设计中的系统。您可以在此区域内声明有界上下文,并在每个区域内应用持续集成以保持统一。但您应该有多少个?它们之间应该有什么关系?答案不像外部系统那样简单,因为我们拥有更多的自由和控制权。
The software your project team is actually building is the system under design. You can declare BOUNDED CONTEXTS within this zone and apply CONTINUOUS INTEGRATION within each to keep them unified. But how many should you have? What relationships should they have to each other? The answers are less cut and dried than with the external systems because we have more freedom and control.
它可能非常简单:为整个设计系统设置一个BOUNDED CONTEXT。例如,对于一个少于 10 人的团队来说,这是一个可能的选择,该团队负责高度相关的功能。
It could be quite simple: a single BOUNDED CONTEXT for the entire system under design. For example, this would be a likely choice for a team of fewer than ten people working on highly interrelated functionality.
随着团队规模的扩大,持续集成可能会变得困难(尽管我见过一些规模稍大的团队也维护它)。您可以寻找一个共享内核,并将相对独立的功能集分解为单独的有界上下文,每个上下文的人员少于 10 人。如果其中两个之间的所有依赖关系都朝着一个方向,您可以设置客户/供应商开发团队。
As the team grows larger, CONTINUOUS INTEGRATION may become difficult (although I have seen it maintained for somewhat larger teams). You may look for a SHARED KERNEL and break off relatively independent sets of functionality into separate BOUNDED CONTEXTS, each with fewer than ten people. If all of the dependencies between two of these go in one direction, you could set up CUSTOMER/SUPPLIER DEVELOPMENT TEAMS.
您可能会发现,两个团队的思维方式截然不同,以至于他们的建模工作不断发生冲突。他们可能实际上需要从模型中获得完全不同的东西,可能只是背景知识的差异,也可能是项目所处的管理结构的结果。如果冲突的原因是你不能改变的,或者你不想改变的,那么你可以选择让模型分道扬镳。在需要集成的地方,两个团队可以共同开发和维护一个转换层,作为持续集成的单点。这与与外部系统的集成形成对比,在外部系统中,反腐败层 通常必须按原样适应另一个系统,并且无法获得另一方的太多支持。
You may recognize that the mind-sets of two groups are so different that their modeling efforts constantly clash. It may be that they actually need quite different things from the model, it may be just a difference in background knowledge, or it may be a result of the management structure the project is embedded in. If the cause of the clash is something you can’t change, or don’t want to change, you may choose to allow the models to go SEPARATE WAYS. Where integration is needed, a translation layer can be developed and maintained jointly by the two teams as the single point of CONTINUOUS INTEGRATION. This is in contrast with integration with external systems, where the ANTICORRUPTION LAYER typically has to accommodate the other system as is and without much support from the other side.
一般来说,每个BOUNDED CONTEXT对应一个团队。一个团队可以维护多个BOUNDED CONTEXT,但多个团队很难(但并非不可能)共同维护一个 BOUNDED CONTEXT。
Generally speaking, there is a correspondence of one team per BOUNDED CONTEXT. One team can maintain multiple BOUNDED CONTEXTS, but it is hard (though not impossible) for multiple teams to work on one together.
同一企业内的不同团队经常会开发自己的专业术语,这些术语可能彼此存在分歧。这些本地术语可能非常精确,并根据他们的需要量身定制。更改它们(例如,通过强制使用标准化、企业范围的术语)需要进行大量培训和分析才能解决差异。即便如此,新术语可能不如他们已有的经过精细调整的版本那么好用。
Different groups within the same business have often developed their own specialized terminologies, which may have diverged from one another. These local jargons may be very precise and tailored to their needs. Changing them (for example, by imposing a standardized, enterprise-wide terminology) requires extensive training and analysis to resolve the differences. Even then, the new terminology may not serve as well as the finely tuned version they already had.
您可以决定在单独的有界上下文中满足这些特殊需求,让模型以不同的方式发展,但翻译层的持续集成除外。通用语言的不同方言将围绕这些模型及其所基于的专业术语发展。如果两种方言有很多重叠,共享内核可以提供所需的专业化,同时最大限度地降低翻译成本。
You may decide to cater to these special needs in separate BOUNDED CONTEXTS, allowing the models to go SEPARATE WAYS, except for CONTINUOUS INTEGRATION of translation layers. Different dialects of the UBIQUITOUS LANGUAGE will evolve around these models and the specialized jargon they are based on. If the two dialects have a lot of overlap, a SHARED KERNEL may provide the needed specialization while minimizing the translation cost.
当不需要整合或整合程度相对有限时,这种方法允许继续使用惯用术语,避免模型被破坏。这种方法也有成本和风险。
Where integration is not needed, or is relatively limited, this allows continued use of customary terminology and avoids corruption of the models. It also has its costs and risks.
• 缺乏共同语言会减少交流。
• The loss of shared language will reduce communication.
• 集成过程中会产生额外开销。
• There is extra overhead in integration.
• 随着同一业务活动和实体的不同模型不断发展,将会出现一些重复工作。
• There will be some duplication of effort, as different models of the same business activities and entities evolve.
但也许最大的风险是,它可能成为反对变革的论据,以及任何古怪、狭隘模式的正当理由。你需要在多大程度上定制系统的这个单独部分以满足特殊需求?最重要的是,这个用户组的特定术语有多大价值?你必须权衡团队更独立行动的价值与翻译的风险,并留意那些没有价值的术语变体的合理化。
But perhaps the biggest risk is that it can become an argument against change and a justification for any quirky, parochial model. How much do you need to tailor this individual part of the system to meet specialized needs? Most important, how valuable is the particular jargon of this user group? You have to weigh the value of more independent action of teams against the risks of translation, keeping an eye out for rationalizing terminology variations that have no value.
有时会出现一种深度模型,可以统一这些不同的语言并满足两组人的需求。问题是,深度模型在生命周期的后期才会出现,即使出现,也需要经过大量的开发和知识积累。你无法计划深度模型;你只能在机会出现时接受它,改变你的策略,然后进行重构。
Sometimes a deep model emerges that can unify these distinct languages and satisfy both groups. The catch is that deep models emerge later in the life cycle, after a lot of development and knowledge crunching, if at all. You can’t plan on a deep model; you just have to accept the opportunity when it arises, change your strategy, and refactor.
请记住,如果集成要求很广泛,翻译成本就会大幅上升。团队之间的一些协调,从对一个具有复杂翻译的对象进行精确修改,到共享内核,可以使翻译更容易,同时仍然不需要完全统一。
Keep in mind that, where integration requirements are extensive, the cost of translation goes way up. Some coordination of the teams, from the pinpoint modifications of one object that has a complicated translation ranging up to a SHARED KERNEL, can make translation easier while still not requiring full unification.
协调复杂系统的打包和部署是一项枯燥乏味的任务,几乎总是比看起来困难得多。BOUNDED CONTEXT策略的选择会对部署产生影响。例如,当客户/供应商团队部署新版本时,他们必须相互协调以发布经过共同测试的版本。代码和数据迁移都必须在这些组合中工作。在分布式系统中,将CONTEXT之间的转换层保持在单个流程中可能会有所帮助,这样您就不会有多个版本共存。
Coordinating the packaging and deployment of complex systems is one of those boring tasks that are almost always a lot harder than they look. The choice of BOUNDED CONTEXT strategy has an impact on the deployment. For example, when CUSTOMER/SUPPLIER TEAMS deploy new versions, they have to coordinate with each other to release versions that have been tested together. Both code and data migrations have to work in these combinations. In a distributed system, it may help to keep the translation layers between CONTEXTS together within a single process, so that you don’t have multiple versions coexisting.
当数据迁移需要时间或分布式系统无法立即更新时,即使部署单个BOUNDED CONTEXT的组件也会很困难,从而导致两个版本的代码和数据共存。
Even deployment of the components of a single BOUNDED CONTEXT can be challenging when data migration takes time or when distributed systems can’t be updated instantaneously, resulting in two versions of the code and data coexisting.
根据部署环境和技术,需要考虑许多技术问题。但BOUNDED CONTEXT关系可以指出热点问题。翻译接口已标记出来。
Many technical considerations come into play depending on the deployment environment and technology. But the BOUNDED CONTEXT relationships can point you toward the hot spots. The translation interfaces have been marked out.
部署计划的可行性应该反馈到上下文边界的绘制中。当两个上下文通过转换层连接时,一个上下文可以更新,这样新的转换层就可以为另一个上下文提供相同的接口。共享内核带来了更大的协调负担,不仅在开发中,而且在部署中也是如此。单独的方式可以让生活变得简单得多。
The feasibility of a deployment plan should feed back into the drawing of the CONTEXT boundaries. When two CONTEXTS are bridged by a translation layer, one CONTEXT can be updated just so a new translation layer provides the same interface to the other CONTEXT. A SHARED KERNEL imposes a much greater burden of coordination, not just in development but also in deployment. SEPARATE WAYS can make life much simpler.
总结这些指导原则,有一系列统一或集成模型的策略。一般来说,您将在无缝集成功能的好处与协调和沟通的额外努力之间进行权衡。您将在更独立的行动与更顺畅的沟通之间进行权衡。更雄心勃勃的统一需要控制所涉及的子系统的设计。
To sum up these guidelines, there is a range of strategies for unifying or integrating models. In general terms, you will trade off the benefits of seamless integration of functionality against the additional effort of coordination and communication. You trade more independent action against smoother communication. More ambitious unification requires control over the design of the subsystems involved.
图 14.13. CONTEXT关系模式的相对需求
Figure 14.13. The relative demands of CONTEXT relationship patterns
最有可能的是,您不是在启动一个项目,而是在寻求改进一个已经在进行中的项目。在这种情况下,第一步是根据现在的情况定义有界上下文 。这至关重要。为了有效,上下文图必须反映团队的真实实践,而不是您可能通过遵循刚刚描述的指导方针决定的理想组织。
Most likely, you are not starting a project but are looking to improve a project that is already under way. In this case, the first step is to define BOUNDED CONTEXTS according to the way things are now. This is crucial. To be effective, the CONTEXT MAP must reflect the true practice of the teams, not the ideal organization you might decide on by following the guidelines just described.
一旦你描述了你当前真正的有界上下文,并描述了它们之间的关系,下一步就是围绕当前组织加强团队的实践。在上下文中改进你的持续集成。将任何杂散的翻译代码重构到你的反腐败层中。命名现有的有界上下文,并确保它们采用项目的通用语言。
Once you have delineated your true current BOUNDED CONTEXTS and described the relationships they currently have, the next step is to tighten up the team’s practices around that current organization. Improve your CONTINUOUS INTEGRATION within the CONTEXTS. Refactor any stray translation code into your ANTICORRUPTION LAYERS. Name the existing BOUNDED CONTEXTS and make sure they are in the UBIQUITOUS LANGUAGE of the project.
现在,您已准备好考虑对边界和关系本身进行更改。这些更改自然会受到我针对新项目描述的相同原则的驱动,但必须将它们分成小部分,务实地进行选择,以最少的努力和干扰获得最大的价值。
Now you are ready to consider changes to the boundaries and relationships themselves. These changes will naturally be driven by the same principles I’ve already described for a new project, but they will have to be bitten off in small pieces, chosen pragmatically to give the most value for the least effort and disruption.
下一节将讨论一旦您决定更改CONTEXT边界,如何进行实际更改。
The next section discusses how to go about actually making changes to your CONTEXT boundaries once you have decided to.
与建模和设计的任何其他方面一样,有关BOUNDED CONTEXTS的决策并不是不可撤销的。不可避免地,在许多情况下您必须更改有关BOUNDED CONTEXTS之间的边界和关系的初始决定。一般而言,拆分CONTEXTS非常容易,但合并它们或更改它们之间的关系却很有挑战性。我将描述一些困难但重要的代表性更改。这些转换通常太大,无法在一次重构甚至一次项目迭代中完成。因此,我已概述了进行这些转换的计划,将其作为一系列可管理的步骤。当然,这些都是指导方针,您必须根据具体情况和事件进行调整。
Like any other aspect of modeling and design, decisions about BOUNDED CONTEXTS are not irrevocable. Inevitably, there will be many cases in which you have to change your initial decision about the boundaries and relationships between BOUNDED CONTEXTS. Generally speaking, breaking up CONTEXTS is pretty easy, but merging them or changing the relationships between them is challenging. I’ll describe a few representative changes that are difficult yet important. These transformations are usually much too big to be taken in a single refactoring or possibly even in a single project iteration. For that reason, I’ve outlined game plans for making these transformations as a series of manageable steps. These are, of course, guidelines that you will have to adapt to your particular circumstances and events.
翻译开销太高。重复太明显。合并BOUNDED CONTEXTS的动机有很多。这很难做到。现在还不算太晚,但需要一些耐心。
Translation overhead is too high. Duplication is too obvious. There are many motivations for merging BOUNDED CONTEXTS. This is hard to do. It’s not too late, but it takes some patience.
即使您的最终目标是完全合并到具有持续集成 ( CONTINUOUS INTEGRATION)的单个上下文 (CONTEXT),也请先转到共享内核 (SHARED KERNEL)。
Even if your eventual goal is to merge completely to a single CONTEXT with CONTINUOUS INTEGRATION, start by moving to a SHARED KERNEL.
1.评估初始情况。在开始将两个CONTEXT相互统一之前,请确保它们在内部确实是统一的。
1. Evaluate the initial situation. Be sure that the two CONTEXTS are indeed internally unified before beginning to unify them with each other.
2.设置流程。您需要决定如何共享代码以及模块命名约定。必须至少每周集成一次共享内核代码。并且必须有一个测试套件。在开发任何共享代码之前设置它。(测试套件将是空的,因此应该很容易通过!)
2. Set up the process. You’ll need to decide how the code will be shared and what the module naming conventions will be. There must be at least weekly integration of the SHARED KERNEL code. And it must have a test suite. Set this up before developing any shared code. (The test suite will be empty, so it should be easy to pass!)
3.选择一些小的子域作为起点 — 在两个CONTEXTS中重复但不属于CORE DOMAIN的部分。第一次合并将建立流程,因此最好使用简单且相对通用或非关键的内容。检查已经存在的集成和翻译。选择正在翻译的内容具有从经过验证的翻译开始的优势,此外,您还可以减少翻译层。
3. Choose some small subdomain to start with—something duplicated in both CONTEXTS, but not part of the CORE DOMAIN. This first merger is going to establish the process, so it is best to use something simple and relatively generic or noncritical. Examine the integrations and translations that already exist. Choosing something that is being translated has the advantage of starting out with a proven translation, plus you’ll be thinning your translation layer.
此时,您有两个针对同一子域的模型。合并的方法基本上有三种。您可以选择一个模型并重构另一个CONTEXT以使其兼容。可以全面做出此决定,设定系统地替换一个CONTEXT 的模型并保留作为一个单元开发的模型的一致性的意图。或者您可以一次选择一个部分,大概最终会得到两者中最好的部分(但要注意不要弄得一团糟)。
At this point, you have two models that address the same subdomain. There are basically three approaches to merging. You can choose one model and refactor the other CONTEXT to be compatible. This decision can be made wholesale, setting the intention of systematically replacing one CONTEXT’S model and retaining the coherence of a model that was developed as a unit. Or you can choose one piece at a time, presumably ending up with the best of both (but taking care not to end up with a jumble).
第三种选择是找到一个新模型,该模型可能比原来的模型更深,能够承担两者的责任。
The third option is to find a new model, presumably deeper than either of the originals, capable of assuming the responsibilities of both.
4.从两个团队中抽取 2 到 4 名开发人员,组成一个小组,为子域制定一个共享模型。无论模型是如何得出的,都必须详细解决。这包括识别同义词和映射任何尚未翻译的术语的艰苦工作。这个联合团队概述了该模型的一组基本测试。
4. Form a group of two to four developers, drawn from both teams, to work out a shared model for the subdomain. Regardless of how the model is derived, it must be ironed out in detail. This includes the hard work of identifying synonyms and mapping any terms that are not already being translated. This joint team outlines a basic set of tests for the model.
5.任一团队的开发人员都负责实施模型(或调整现有代码以供共享)、制定细节并使其发挥作用。如果这些开发人员在模型中遇到问题,他们会从第 3 步重新召集团队并参与对概念的任何必要修订。
5. Developers from either team take on the task of implementing the model (or adapting existing code to be shared), working out details and making it function. If these developers run into problems with the model, they reconvene the team from step 3 and participate in any necessary revisions of the concepts.
6.各团队开发人员承担与新SHARED KERNEL集成的任务。
6. Developers of each team take on the task of integrating with the new SHARED KERNEL.
7. Remove translations that are no longer needed.
此时,您将拥有一个非常小的共享内核,并有一个流程来维护它。在后续的项目迭代中,重复步骤 3 到 7 以共享更多内容。随着流程的完善和团队信心的增强,您可以同时处理更复杂的子域、多个子域或核心域中的子域。
At this point, you will have a very small SHARED KERNEL, with a process in place to maintain it. In subsequent project iterations, repeat steps 3 through 7 to share more. As the processes firm up and the teams gain confidence, you can take on more complicated subdomains, multiple ones at the same time, or subdomains that are in the CORE DOMAIN.
注意:当您采用更多领域特定模型部分时,您可能会遇到两种模型符合不同用户社区的专业术语的情况。明智的做法是推迟将它们合并到共享内核中,除非在深度模型方面取得突破,为您提供能够取代这两种专业模型的语言。共享内核的一个优点是,您可以获得持续集成的一些优势,同时保留独立方式的一些优势。
A note: As you take on more domain-specific parts of the models, you may encounter cases where the two models have conformed to the specialized jargon of different user communities. It is wise to defer merging these into the SHARED KERNEL unless a breakthrough to a deep model has occurred, providing you with a language capable of superseding both specialized ones. An advantage of a SHARED KERNEL is that you can have some of the advantages of CONTINUOUS INTEGRATION while retaining some of the advantages of SEPARATE WAYS.
这些是合并到SHARED KERNEL 的一些准则。 在继续之前,请先考虑一种可以满足此转换所解决的部分需求的替代方案。 如果两种模型中肯定有一种是首选,请考虑转向它而不进行集成。 不需要共享公共子域,只需通过重构应用程序以调用更受欢迎的CONTEXT模型并进行该模型所需的任何增强,系统地将这些子域的全部责任从一个BOUNDED CONTEXT转移到另一个 BOUNDED CONTEXT 。 在没有任何持续集成开销的情况下,您已经消除了冗余。 可能(但不一定),更受欢迎的BOUNDED CONTEXT最终会完全接管,并且您将创建与合并相同的效果。 在过渡期间(可能很长或无限期),这将具有采用SEPARATE WAYS的通常优点和缺点,您必须权衡它们与SHARED KERNEL的利弊。
Those are some guidelines for merging into a SHARED KERNEL. Before going ahead, consider one alternative that satisfies some of the needs addressed by this transformation. If one of the two models is definitely preferred, consider shifting toward it without integrating. Instead of sharing common subdomains, just systematically transfer full responsibility for those subdomains from one BOUNDED CONTEXT to the other by refactoring the applications to call on the model of the more favored CONTEXT, and making any enhancements that model needs. Without any ongoing integration overhead, you have eliminated redundancy. Potentially (but not necessarily), the more favored BOUNDED CONTEXT could eventually take over completely, and you’ll have created the same effect as a merger. In the transition (which can be quite long or indefinite), this will have the usual advantages and disadvantages of going SEPARATE WAYS, and you have to weigh them against the pros and cons of a SHARED KERNEL.
如果您的SHARED KERNEL正在扩展,您可能会被两个BOUNDED CONTEXTS完全统一的优势所吸引。这不仅仅是解决模型差异的问题。您将改变团队结构,并最终改变人们使用的语言。
If your SHARED KERNEL is expanding, you may be lured by the advantages of full unification of the two BOUNDED CONTEXTS. This is not just a matter of resolving the model differences. You are going to be changing team structures and ultimately the language people speak.
Start by preparing the people and the teams.
1.确保每个团队都分别实施了持续集成所需的所有流程(共享代码所有权、频繁集成等)。协调两个团队的集成程序,以便每个人都以相同的方式做事。
1. Be sure that all the processes needed for CONTINUOUS INTEGRATION (shared code ownership, frequent integration, and so on) are in place on each team, separately. Harmonize integration procedures on the two teams so that everyone is doing things in the same way.
2.开始在团队之间轮换团队成员。这将创建一个了解两种模式的人才库,并开始将两个团队的人员联系起来。
2. Start circulating team members between teams. This will create a pool of people who understand both models, and will begin to connect the people of the two teams.
3.分别阐明每个模型的提炼。(参见第15章)
3. Clarify the distillation of each model individually. (See Chapter 15.)
4.此时,信心应该足够高,可以开始将核心域合并到共享内核中。这可能需要多次迭代,有时需要在新共享的部分和尚未共享的部分之间建立临时转换层。一旦开始合并核心域,最好快速完成。这是一个高开销阶段,容易出错,应尽可能缩短,优先于大多数新开发。但不要承担超出您能力范围的任务。
4. At this point, confidence should be high enough to begin merging the core domain into the SHARED KERNEL. This can take several iterations, and sometimes temporary translation layers are needed between the newly shared parts and the not-yet-shared parts. Once into merging the CORE DOMAIN, it is best to go pretty fast. It is a high-overhead phase, fraught with errors, and should be shortened as much as possible, taking priority over most new development. But don’t take on more than you can handle.
要合并CORE模型,您有几种选择。您可以坚持使用一种模型并修改另一种模型以与其兼容,也可以创建子域的新模型并调整两个上下文以使用它。注意这两个模型是否已针对不同的用户需求进行了定制。您可能需要两个原始模型的专门功能。这就要求开发一个可以取代两个原始模型的更深层的模型。开发更深层的统一模型非常困难,但如果您致力于完全合并这两个上下文,那么您就不再有多种方言的选择。在最终模型和代码的集成清晰度方面,将会有所回报。请注意,不要以满足用户专门需求的能力为代价。
To merge the CORE models, you have a few choices. You can stick with one model and modify the other to be compatible with it, or you can create a new model of the subdomain and adapt both contexts to use it. Watch out if the two models have been tailored to address distinct user needs. You may need the specialized power of both original models. This calls for developing a deeper model that can supersede both original models. Developing a deeper unifying model is very difficult, but if you are committed to the full merger of the two CONTEXTS, you no longer have the option of multiple dialects. There will be a reward in terms of the clarity of integration of the resulting model and code. Be careful that it doesn’t come at the cost of your ability to address the specialized needs of your users.
5.随着共享内核的增长,将集成频率增加到每日,最终增加到持续集成。
5. As the SHARED KERNEL grows, increase the integration frequency to daily and finally to CONTINUOUS INTEGRATION.
6. 当共享内核接近包含前两个有界上下文时,你会发现自己要么有一个大团队,要么有两个小团队,它们拥有一个共享的代码库,它们不断地整合,并且频繁地来回交换成员。
6. As the SHARED KERNEL approaches the point of encompassing all of the two former BOUNDED CONTEXTS, you will find yourself with either one large team or two smaller teams that have a shared code base that they INTEGRATE CONTINUOUSLY, and that trade members back and forth frequently.
一切美好的事物都有终结的一天,即使是旧式计算机软件。但终结不会自动发生。这些旧系统可能与业务和其他系统紧密相关,以至于要花很多年才能摆脱它们。幸运的是,这不必一下子完成。
All good things must come to an end, even legacy computer software. But it doesn’t happen on its own. These old systems can be so woven into the business and other systems that extricating them can take many years. Fortunately, it doesn’t have to be done all at once.
可能性太多了,我在这里只能浅尝辄止。但我将讨论一个常见案例:业务中每天使用的旧系统最近被一些更现代的系统所补充,这些系统通过 ANTICORRUPTION LAYER与遗留系统进行通信。
The possibilities are too various for me to do more than scratch the surface here. But I’ll discuss a common case: An old system that is used daily in the business has been supplemented recently by a handful of more modern systems that communicate with the legacy system through an ANTICORRUPTION LAYER.
第一步应该是确定测试策略。应该为新系统中的新功能编写自动化单元测试,但逐步淘汰旧系统会带来特殊的测试需求。一些组织会在一段时间内同时运行新旧系统。
One of the first steps should be to decide on a testing strategy. Automated unit tests should be written for new functionality in the new systems, but phasing out legacy introduces special testing needs. Some organizations run new and old in parallel for some period of time.
在任何给定的迭代中:
In any given iteration:
1.确定可以在一次迭代中添加到某个受青睐系统的特定遗留功能。
1. Identify specific functionality of the legacy that could be added to one of the favored systems within a single iteration.
2.确定防腐层中需要添加的内容。
2. Identify additions that will be required in the ANTICORRUPTION LAYER.
3.实施。
3. Implement.
4.部署。
4. Deploy.
有时,需要花费多次迭代来为可以逐步淘汰的单元编写等效功能,但仍在小型、迭代大小的单元中规划新功能,仅等待多次迭代即可部署。
Sometimes it will be necessary to spend more than one iteration writing equivalent functionality to a unit that can be phased out of the legacy, but still plan the new functions in small, iteration-sized units, only waiting multiple iterations for deployment.
部署是另一个存在太多变化而无法覆盖所有基础的点。如果这些小的、增量的变化可以推广到生产中,对开发来说会很好,但通常需要组织更大的发布。必须培训用户使用新软件。有时必须成功完成一个并行期。必须解决许多后勤问题。
Deployment is another point at which too much variation exists to cover all the bases. It would be nice for development if these small, incremental changes could be rolled out to production, but usually it is necessary to organize bigger releases. The users must be trained to use the new software. A parallel period sometimes must be completed successfully. Many logistical problems will have to be worked out.
一旦它最终在田野中奔跑:
Once it is finally running in the field:
5.找出防腐层中任何不必要的部分并将其去除。
5. Identify any unnecessary parts of the ANTICORRUPTION LAYER and remove them.
6.考虑移除遗留系统中现在未使用的模块,尽管这可能并不实际。具有讽刺意味的是,遗留系统设计得越好,淘汰就越容易。但设计不良的软件很难一点一点地拆除。也许可以忽略未使用的部分,直到以后剩余部分被淘汰,整个系统就可以关闭。
6. Consider excising the now-unused modules of the legacy system, though this may not turn out to be practical. Ironically, the better designed the legacy system is, the easier it will be to phase it out. But badly designed software is hard to dismantle a little at a time. It may be possible to just ignore the unused parts until a later time when the remainder has been phased out and the whole thing can be switched off.
重复这一过程。遗留系统应该会越来越少地参与到业务中,最终将有可能看到隧道尽头的曙光,并最终关闭旧系统。与此同时,随着各种组合增加或减少系统之间的相互依赖性, ANTICORRUPTION LAYER将交替缩小和膨胀。当然,在其他所有条件相同的情况下,您应该首先迁移那些导致ANTICORRUPTION LAYER较小的功能。但其他因素可能会占主导地位,并且您可能不得不在某些过渡期间忍受一些棘手的转换。
Repeat this over and over. The legacy system should become less involved in the business, and eventually it will be possible to see the light at the end of the tunnel and finally switch off the old system. Meanwhile, the ANTICORRUPTION LAYER will alternately shrink and swell as various combinations increase or decrease the interdependence between the systems. All else being equal, of course, you should migrate first those functions that lead to smaller ANTICORRUPTION LAYERS. But other factors are likely to dominate, and you may have to live with some hairy translations during some transitions.
您一直在使用一系列临时协议与其他系统进行集成,但随着越来越多的系统需要访问,维护负担也越来越重,或者交互变得非常难以理解。您需要使用已发布的语言来形式化系统之间的关系。
You have been integrating with other systems with a series of ad hoc protocols, but the maintenance burden is mounting as more systems want access, or perhaps the interaction is becoming very difficult to understand. You need to formalize the relationship between the systems with a PUBLISHED LANGUAGE.
1.如果有行业标准语言,请对其进行评估并尽可能使用它。
1. If an industry-standard language is available, evaluate it and use it if at all possible.
2.如果没有可用的标准或预发布语言,则首先完善将作为主机的系统的核心域。(参见第 15 章。)
2. If no standard or prepublished language is available, then begin by sharpening up the CORE DOMAIN of the system that will serve as the host. (See Chapter 15.)
3.使用核心域 作为交换语言的基础,尽可能使用标准交换范例,例如 XML。
3. Use the CORE DOMAIN as the basis of an interchange language, using a standard interchange paradigm such as XML, if at all possible.
4.向所有参与合作的人发布新语言(至少)。
4. Publish the new language to all involved in the collaboration (at least).
5.如果涉及新的系统架构,也请发布。
5. If a new system architecture is involved, publish that too.
6.为每个协作系统构建翻译层。
6. Build translation layers for each collaborating system.
7.切换。
7. Switch over.
此时,其他合作者应该能够在尽量减少干扰的情况下进入。
At this point, additional collaborators should be able to enter with minimal disruption.
请记住,已发布的语言必须是稳定的,但在继续进行不懈的重构时,您仍然需要自由更改主机的模型。因此,不要将交换语言和主机的模型等同起来。将它们保持紧密联系将减少翻译开销,并且您可以选择使主机成为CONFORMIST。但如果成本效益权衡有利于此,请保留加强翻译层和分歧的权利。
Remember, the PUBLISHED LANGUAGE must be stable, yet you’ll still need the freedom to change the host’s model as you continue your relentless refactoring. Therefore, do not equate the interchange language and the model of the host. Keeping them close together will reduce translation overhead, and you may choose to make your host a CONFORMIST. But reserve the right to beef up the translation layer and diverge if the cost-benefit trade-off favors that.
项目负责人应根据功能集成需求和开发团队关系定义有界上下文。一旦明确定义并遵守有界上下文和上下文图,就应该保护逻辑一致性。相关的沟通问题至少会暴露出来,以便处理。
Project leaders should define BOUNDED CONTEXTS based on functional integration requirements and relationships of development teams. Once BOUNDED CONTEXTS and a CONTEXT MAP are explicitly defined and respected, then logical consistency should be protected. Related communication problems will at least be exposed so they can be dealt with.
然而,有时模型环境(无论是有意识地限制的还是自然发生的)被误用来解决系统内逻辑不一致以外的问题。团队可能会发现大型环境的模型似乎太复杂,无法整体理解或完全分析。出于选择或偶然,这通常会导致将环境分解为更易于管理的部分。这种碎片化会导致失去机会。现在,值得仔细审查在广泛环境中建立大型模型的决定,如果在组织上或政治上不可能将其保持在一起,如果它实际上是碎片化的,那么重新绘制地图并定义你可以保留的边界。但是,如果大型BOUNDED CONTEXT解决了迫切的集成需求,并且如果除了模型本身的复杂性之外它似乎是可行的,那么拆分 CONTEXT可能不是最好的答案。
However, sometimes model contexts, whether consciously bounded or naturally occurring, are misapplied to solve problems other than logical inconsistency within a system. The team may find that the model of a large CONTEXT seems too complex to comprehend as a whole, or to analyze completely. By choice or by chance, this often leads to breaking down the CONTEXTS into more manageable pieces. This fragmentation leads to lost opportunities. Now, it is worth scrutinizing a decision to establish a large model in a broad CONTEXT, and if it is not organizationally or politically possible to keep together, if it is in reality fragmenting, then redraw the map and define boundaries you can keep. But if a large BOUNDED CONTEXT addresses compelling integration needs, and if it seems feasible apart from the complexity of the model itself, then breaking up the CONTEXT may not be the best answer.
在做出这种牺牲之前,还应该考虑其他使大型模型易于处理的方法。接下来的两章将重点介绍如何通过应用两个更广泛的原则来管理大型模型中的复杂性:提炼和大规模结构。
There are other means of making large models tractable that should be considered before making this sacrifice. The next two chapters focus on managing complexity within a big model by applying two more broad principles: distillation and large-scale structure.
—詹姆斯·克拉克·麦克斯韦,《电磁论》,1873 年
—James Clerk Maxwell, A Treatise on Electricity and Magnetism, 1873
这四个方程及其术语的定义和所依据的数学主体表达了整个十九世纪古典电磁学。
These four equations, along with the definitions of their terms and the body of mathematics they rest on, express the entirety of classical nineteenth-century electromagnetism.
如何集中精力解决核心问题,避免淹没在支线问题的海洋中?分层架构将领域概念与计算机系统运行的技术逻辑分离开来,但在大型系统中,即使是孤立的领域也可能复杂到难以管理。
How do you focus on your central problem and keep from drowning in a sea of side issues? A LAYERED ARCHITECTURE separates domain concepts from the technical logic that makes a computer system run, but in a large system, even the isolated domain may be unmanageably complex.
提炼是分离混合物成分以提取精华的过程,使其更有价值和实用。模型是知识的提炼。每次重构以加深洞察力时,我们都会抽象出领域知识和优先级的一些关键方面。现在,退一步来看战略观点,本章将介绍区分模型的广泛部分并提炼整个领域模型的方法。
Distillation is the process of separating the components of a mixture to extract the essence in a form that makes it more valuable and useful. A model is a distillation of knowledge. With every refactoring to deeper insight, we abstract some crucial aspect of domain knowledge and priorities. Now, stepping back for a strategic view, this chapter looks at ways to distinguish broad swaths of the model and distill the domain model as a whole.
与许多化学蒸馏一样,分离出的副产品本身通过蒸馏过程变得更有价值(作为通用子域和相关机制),但其动机是希望提取出一个特别有价值的部分,这个部分使我们的软件与众不同并且值得构建:“核心领域”。
As with many chemical distillations, the separated by-products are themselves made more valuable by the distillation process (as GENERIC SUBDOMAINS and COHERENT MECHANISMS), but the effort is motivated by the desire to extract that one particularly valuable part, the part that distinguishes our software and makes it worth building: the “CORE DOMAIN.”
领域模型的战略提炼包括以下几点:
Strategic distillation of a domain model does all of the following:
1.帮助所有团队成员掌握系统的整体设计以及系统如何协同工作
1. Aids all team members in grasping the overall design of the system and how it fits together
2.通过确定可管理大小的核心模型来进入通用语言,从而促进沟通
2. Facilitates communication by identifying a core model of manageable size to enter the UBIQUITOUS LANGUAGE
3.指导重构
3. Guides refactoring
4.重点关注模型中最有价值的领域
4. Focuses work on areas of the model with the most value
5.指导外包、现成组件的使用以及任务分配决策
5. Guides outsourcing, use of off-the-shelf components, and decisions about assignments
本章阐述了对核心领域进行战略提炼的系统方法,并解释了如何在团队内有效地分享对核心领域的看法,并提供了讨论我们正在做的事情的语言。
This chapter lays out a systematic approach to strategic distillation of the CORE DOMAIN, and it explains how to effectively share a view of it within the team and provides the language to talk about what we are doing.
图 15.1. 战略提炼的导航图
Figure 15.1. A navigation map for strategic distillation
就像园丁修剪树木、为枝条的生长扫清道路一样,我们将运用一系列技术来消除模型中的干扰,并将注意力集中在最重要的部分。...
Like a gardener pruning a tree, clearing the way for the growth of the main branches, we are going to apply a suite of techniques to hew away distractions in the model and focus our attention on the part that matters most. . . .
在设计大型系统时,有如此之多的组件,它们都很复杂,而且对于成功来说都是绝对必要的,以至于领域模型的本质,即真正的业务资产,可能会被掩盖和忽视。
In designing a large system, there are so many contributing components, all complicated and all absolutely necessary to success, that the essence of the domain model, the real business asset, can be obscured and neglected.
难以理解的系统很难改变。改变的影响很难预见。如果开发人员走出自己熟悉的领域,就会迷失方向。(在将新人带入团队时尤其如此,但即使是团队的老成员也会遇到困难,除非代码非常具有表现力和条理性。)这迫使人们专业化。当开发人员将他们的工作限制在特定模块时,知识传递会进一步减少。随着工作划分,系统的顺利集成会受到影响,分配工作的灵活性也会丧失。当开发人员没有意识到某种行为已经存在于其他地方时,就会出现重复,因此系统变得更加复杂。
A system that is hard to understand is hard to change. The effect of a change is hard to foresee. A developer who wanders outside his or her own area of familiarity gets lost. (This is particularly true when bringing new people into a team, but even an established member of the team will struggle unless code is very expressive and organized.) This forces people to specialize. When developers confine their work to specific modules, it further reduces knowledge transfer. With the compartmentalization of work, smooth integration of the system suffers, and flexibility in assigning work is lost. Duplication crops up when a developer does not realize that a behavior already exists elsewhere, and so the system becomes even more complex.
这些都是任何难以理解的设计所导致的一些后果,但是,失去领域全局还会带来另一个同样严重的风险:
Those are some of the consequences of any design that is hard to understand, but there is another, equally serious risk from losing the big picture of the domain:
残酷的现实是,设计的各个部分并非都能同样精致。必须设定优先级。要使领域模型成为一项资产,该模型的关键核心必须精巧且充分利用来创建应用程序功能。但稀缺的高技能开发人员往往会倾向于技术基础设施或清晰定义的领域问题,这些问题无需专业领域知识即可理解。
The harsh reality is that not all parts of the design are going to be equally refined. Priorities must be set. To make the domain model an asset, the model’s critical core has to be sleek and fully leveraged to create application functionality. But scarce, highly skilled developers tend to gravitate to technical infrastructure or neatly definable domain problems that can be understood without specialized domain knowledge.
计算机科学家似乎对系统的这些部分很感兴趣,并认为它们可以培养可转移的专业技能并提供更好的简历材料。专业核心,即模型中真正区分应用程序并使其成为业务资产的部分,通常最终由技能较低的开发人员与 DBA 合作创建数据模式,然后逐个功能地编写代码,而根本不利用模型中的任何概念能力。
Such parts of the system seem interesting to computer scientists, and are perceived to build transferable professional skills and provide better resume material. The specialized core, that part of the model that really differentiates the application and makes it a business asset, typically ends up being put together by less skilled developers who work with DBAs to create a data schema and then code feature-by-feature without drawing on any conceptual power in the model at all.
软件这一部分的设计或实现不佳会导致应用程序无法为用户提供令人信服的功能,无论技术基础结构运行得如何良好,无论支持功能有多好。当项目缺乏对整体设计的清晰认识以及各部分的相对重要性时,这种隐蔽的问题就会根深蒂固。
Poor design or implementation of this part of the software leads to an application that never does compelling things for the users, no matter how well the technical infrastructure works, no matter how nice the supporting features are. This insidious problem can take root when a project lacks a sharp picture of the overall design and the relative significance of the various parts.
我参与过的一个最成功的项目最初就患有这种综合症。该项目的目标是开发一个非常复杂的银团贷款系统。大多数优秀人才都乐于从事数据库映射层和消息传递接口的工作,而业务模型则掌握在对象技术新手的手中。
One of the most successful projects I’ve joined initially suffered from this syndrome. The goal was to develop a very complex syndicated loan system. Most of the strong talent was happily working on database mapping layers and messaging interfaces while the business model was in the hands of developers new to object technology.
唯一的例外是一位经验丰富的对象开发人员,他正在处理一个领域问题,他设计了一种将注释附加到任何长期存在的领域对象的方法。这些注释可以组织起来,以便交易员可以看到他们或其他人为过去的某个决策记录的理由。他还构建了一个优雅的用户界面,可以直观地访问注释模型的灵活功能。
The single exception, an experienced object developer working on a domain problem, devised a way of attaching comments to any of the long-lived domain objects. These comments could be organized so that traders could see the rationale they or others recorded for some past decision. He also built an elegant user interface that gave intuitive access to the flexible features of the comment model.
这些功能非常实用,而且设计精良。它们已投入生产。
These features were useful and well designed. They went into production.
不幸的是,这些功能都只是次要的。这位才华横溢的开发人员模仿了他有趣而通用的评论方式,干净利落地实现了它,并把它交到了用户手中。与此同时,一位不称职的开发人员把任务关键型的“贷款”模块变成了一个难以理解的混乱局面,项目几乎无法恢复。
Unfortunately, they were peripheral. This talented developer modeled his interesting, generic way of commenting, implemented it cleanly, and put it into users’ hands. Meanwhile an incompetent developer was turning the mission-critical “loan” module into an incomprehensible tangle that the project very nearly did not recover from.
规划过程必须将资源投入到模型和设计中最关键的点。要做到这一点,规划和开发过程中的每个人都必须识别和理解这些点。
The planning process must drive resources to the most crucial points in the model and design. To do that, those points must be identified and understood by everyone during planning and development.
模型中那些与预期应用目的相关且独特的部分构成了核心域。核心域是系统中应增加最多价值的地方。
Those parts of the model distinctive and central to the purposes of the intended applications make up the CORE DOMAIN. The CORE DOMAIN is where the most value should be added in your system.
所以:
Therefore:
将模型精简。找到核心域并提供一种轻松将其与大量支持模型和代码区分开来的方法。将最有价值和最专业的概念清晰地呈现出来。将核心缩小。
Boil the model down. Find the CORE DOMAIN and provide a means of easily distinguishing it from the mass of supporting model and code. Bring the most valuable and specialized concepts into sharp relief. Make the CORE small.
将顶尖人才派往核心领域,并进行相应的招聘。在核心领域投入精力,寻找深度模型并开发灵活的设计——足以实现系统的愿景。通过它如何支持精炼的核心来证明对任何其他部分的投资是合理的。
Apply top talent to the CORE DOMAIN, and recruit accordingly. Spend the effort in the CORE to find a deep model and develop a supple design—sufficient to fulfill the vision of the system. Justify investment in any other part by how it supports the distilled CORE.
提炼核心领域并不容易,但它确实可以让你做出一些简单的决定。你将投入大量精力使你的核心与众不同,同时保持设计的其余部分尽可能通用。如果你需要将设计的某些方面保密以作为竞争优势,那么它就是核心领域。没有必要浪费精力隐藏其余部分。每当必须在两个理想的重构之间做出选择时(由于时间限制),应该首先选择对核心领域影响最大的那个。
Distilling the CORE DOMAIN is not easy, but it does lead to some easy decisions. You’ll put a lot of effort into making your CORE distinctive, while keeping the rest of the design as generic as is practical. If you need to keep some aspect of your design secret as a competitive advantage, it is the CORE DOMAIN. There is no need to waste effort concealing the rest. And whenever a choice has to be made (due to time limitations) between two desirable refactorings, the one that most affects the CORE DOMAIN should be chosen first.
本章中的模式使核心域更易于查看、使用和更改。
The patterns in this chapter make the CORE DOMAIN easier to see and use and change.
我们正在研究模型中那些特别能代表您的业务领域和解决您的业务问题的部分。
We are looking at those parts of the model particular to representing your business domain and solving your business problems.
您选择的核心领域取决于您的观点。例如,许多应用程序需要一个通用的货币模型来表示各种货币及其汇率和转换。另一方面,支持货币交易的应用程序可能需要更复杂的货币模型,这将被视为核心的一部分。即使在这种情况下,货币模型的一部分也可能非常通用。随着经验的积累,对该领域的了解不断加深,提炼过程可以继续进行,分离通用货币概念并仅保留核心领域中模型的专门方面。
The CORE DOMAIN you choose depends on your point of view. For example, many applications need a generic model of money that could represent various currencies and their exchange rates and conversions. On the other hand, an application to support currency trading might need a more elaborate model of money, which would be considered part of the CORE. Even in such a case, there may be a part of the money model that is very generic. As insight into the domain deepens with experience, the distillation process can continue by separating the generic money concepts and retaining only the specialized aspects of the model in the CORE DOMAIN.
在航运应用中,CORE可以作为货物如何整合运输、集装箱转手时责任如何转移或特定集装箱如何通过各种传输方式到达目的地。在投资银行业务中,CORE可以包括受让人和参与者之间的资产联合模式。
In a shipping application, the CORE could be the model of how cargoes are consolidated for shipping, how liability is transferred when containers change hands, or how a particular container is routed on various transports to reach its destination. In investment banking, the CORE could include the models of syndication of assets among assignees and participants.
一个应用程序的核心域是另一个应用程序的通用支持组件。不过,在整个项目中,通常在整个公司中,可以定义一致的核心。与设计的其他部分一样,核心域的识别应该通过迭代来发展。一组特定关系的重要性可能一开始并不明显。最初看起来显然是核心的对象可能最终起到辅助作用。
One application’s CORE DOMAIN is another application’s generic supporting component. Still, throughout one project, and usually throughout one company, a consistent CORE can be defined. Like every other part of the design, the identification of the CORE DOMAIN should evolve through iterations. The importance of a particular set of relationships might not be apparent at first. The objects that seem obviously central at first may turn out to have supporting roles.
以下章节中的讨论,特别是通用子域,将为这些决策提供更多指导。
The discussion in the following sections, particularly GENERIC SUBDOMAINS, will give more guidelines for these decisions.
项目团队中技术最精湛的成员很少具备该领域的大量知识。这限制了他们的实用性,并加剧了将他们分配到支持组件的倾向,从而形成了一种恶性循环,即缺乏知识使他们无法从事需要积累领域知识的工作。
The most technically proficient members of project teams seldom have much knowledge of the domain. This limits their usefulness and reinforces the tendency to assign them to supporting components, sustaining a vicious circle in which lack of knowledge keeps them away from the work that would build domain knowledge.
要打破这种循环,必须组建一支团队,将一批具有长期承诺并有兴趣成为领域知识库的优秀开发人员与一个或多个对业务了如指掌的领域专家配对。如果认真对待,领域设计是一项有趣且具有技术挑战性的工作,而且有很多开发人员都这样看待它。
It is essential to break this cycle by assembling a team matching up a set of strong developers who have a long-term commitment and an interest in becoming repositories of domain knowledge with one or more domain experts who know the business deeply. Domain design is interesting, technically challenging work when approached seriously, and developers can be found who see it this way.
聘请短期的外部设计专家来完成创建核心域的具体工作通常是不切实际的,因为团队需要积累领域知识,而临时成员只是杯水车薪。另一方面,担任教学/指导角色的专家可以非常有价值,可以帮助团队建立其领域设计技能并促进使用团队成员可能尚未掌握的复杂原则。
It is usually not practical to hire short-term, outside design expertise for the nuts and bolts of creating the CORE DOMAIN, because the team needs to accumulate domain knowledge, and a temporary member is a leak in the bucket. On the other hand, an expert in a teaching/mentoring role can be very valuable by helping the team build its domain design skills and facilitating the use of sophisticated principles that team members probably have not mastered.
出于类似的原因, CORE DOMAIN不太可能被购买。人们已经努力构建行业特定的模型框架,最突出的例子是半导体行业联盟 SEMATECH 为半导体制造自动化开发的 CIM 框架,以及 IBM 为各种企业开发的“旧金山”框架。虽然这是一个非常诱人的想法,到目前为止,结果并不引人注目,除非作为促进数据交换的已发布语言(见第 14 章)。《领域特定应用程序框架》(Fayad 和 Johnson 2000 )一书概述了这一领域的现状。随着该领域的发展,可能会出现更多可行的框架。
For similar reasons, it is unlikely that the CORE DOMAIN can be purchased. Efforts have been made to build industry-specific model frameworks, conspicuous examples being the semiconductor industry consortium SEMATECH’s CIM framework for semiconductor manufacturing automation, and IBM’s “San Francisco” frameworks for a wide range of businesses. Although this is a very enticing idea, so far the results have not been compelling, except perhaps as PUBLISHED LANGUAGES facilitating data interchange (see Chapter 14). The book Domain-Specific Application Frameworks (Fayad and Johnson 2000) gives an overview of the state of this art. As the field advances, more workable frameworks may be available.
即便如此,还有一个更根本的谨慎理由:定制软件的最大价值来自于对核心域的完全控制。精心设计的框架可能能够提供高级抽象,您可以专门使用它。它可以节省您开发更通用的部分的时间,让您可以专注于核心。但如果它对您限制更多,那么可能存在三种可能性。
Even so, there is a more fundamental reason for caution: The greatest value of custom software comes from the total control of the CORE DOMAIN. A well-designed framework may be able to provide high-level abstractions that you can specialize for your use. It may save you from developing the more generic parts and leave you free to concentrate on the CORE. But if it constrains you more than that, then there are three likely possibilities.
1.您正在失去一项重要的软件资产。请撤回您核心域中的限制性框架。
1. You are losing an essential software asset. Back off restrictive frameworks in your CORE DOMAIN.
2.框架处理的区域并不像您想象的那么重要。将CORE DOMAIN的边界重新绘制到模型中真正独特的部分。
2. The area treated by the framework is not as pivotal as you thought. Redraw the boundaries of the CORE DOMAIN to the truly distinctive part of the model.
3.您的“核心领域”没有特殊需求。考虑采用风险较低的解决方案,例如购买软件来与您的应用程序集成。
3. You don’t have special needs in your CORE DOMAIN. Consider a lower-risk solution, such as purchasing software to integrate with your applications.
不管怎样,创建独特的软件需要一支稳定的团队积累专业知识并将其转化为丰富的模型。没有捷径。没有灵丹妙药。
One way or another, creating distinctive software comes back to a stable team accumulating specialized knowledge and crunching it into a rich model. No shortcuts. No magic bullets.
本章其余部分所介绍的各种提炼技术几乎可以按照任何顺序应用,但它们对设计的修改程度存在一定范围。
The various distillation techniques that make up the rest of this chapter can be applied in almost any order, but there is a range in how radically they modify the design.
简单的领域愿景声明以最低的投资传达了基本概念及其价值。突出的核心可以改善沟通并帮助指导决策——并且仍然几乎不需要对设计进行任何修改。
A simple DOMAIN VISION STATEMENT communicates the basic concepts and their value with a minimum investment. The HIGHLIGHTED CORE can improve communication and help guide decision making—and still requires little or no modification to the design.
更积极的重构和重新打包明确分离了通用子域,然后可以单独处理。凝聚性机制可以用多功能、可沟通的、和灵活的设计。消除这些干扰可以解开CORE。
More aggressive refactoring and repackaging explicitly separate GENERIC SUBDOMAINS, which can then be dealt with individually. COHESIVE MECHANISMS can be encapsulated with versatile, communicative, and supple design. Removing these distractions disentangles the CORE.
重新打包SEGREGATED CORE使得CORE直接可见,甚至在代码中也可见,并且有助于将来在CORE模型上开展工作。
Repackaging a SEGREGATED CORE makes the CORE directly visible, even in the code, and facilitates future work on the CORE model.
其中最雄心勃勃的是抽象核心,它以纯粹的形式表达最基本的概念和关系(并且需要对模型进行大量的重组和重构)。
And most ambitious is the ABSTRACT CORE, which expresses the most fundamental concepts and relationships in a pure form (and requires extensive reorganizing and refactoring of the model).
这些技术都需要不断加大投入,但刀刃越磨越锋利。领域模型的不断提炼可以产生一种资产,为项目提供速度、灵活性和执行精度。
Each of these techniques requires a successively greater commitment, but a knife gets sharper as its blade is ground finer. Successive distillation of a domain model produces an asset that gives the project speed, agility, and precision of execution.
首先,我们可以提炼出该模型最不具特色的方面。通用子域与核心域形成对比,阐明了每个子域的含义。...
To start, we can boil off the least distinctive aspects of the model. GENERIC SUBDOMAINS provide a contrast to the CORE DOMAIN that clarifies the meaning of each. . . .
模型的某些部分增加了复杂性,但并未捕捉或传达专业知识。任何无关紧要的东西都会使核心领域更难辨别和理解。模型充斥着每个人都知道的一般原则或属于专业的细节,这些细节不是您的主要关注点,但起着辅助作用。然而,无论多么通用,这些其他元素对于系统的运行和模型的完整表达都是必不可少的。
Some parts of the model add complexity without capturing or communicating specialized knowledge. Anything extraneous makes the CORE DOMAIN harder to discern and understand. The model clogs up with general principles everyone knows or details that belong to specialties which are not your primary focus but play a supporting role. Yet, however generic, these other elements are essential to the functioning of the system and the full expression of the model.
您希望模型中有一部分是理所当然的。它无疑是领域模型的一部分,但它抽象出了许多企业可能需要的概念。例如,航运、银行或制造业等各种企业都需要某种形式的企业组织结构图。再举一个例子,许多应用程序跟踪应收账款、费用分类账和其他财务事项,这些都可以使用通用会计模型来处理。
There is a part of your model that you would like to take for granted. It is undeniably part of the domain model, but it abstracts concepts that would probably be needed for a great many businesses. For example, a corporate organization chart is needed in some form by businesses as diverse as shipping, banking, or manufacturing. For another example, many applications track receivables, expense ledgers, and other financial matters that could all be handled using a generic accounting model.
通常,大量的精力都花在了领域中的外围问题上。我亲眼目睹过两个不同的项目,它们雇佣了他们最好的开发人员数周时间重新设计带有时区的日期和时间。虽然这些组件必须起作用,但它们并不是系统的概念核心。
Often a great deal of effort is spent on peripheral issues in the domain. I personally have witnessed two separate projects that have employed their best developers for weeks in redesigning dates and times with time zones. While such components must work, they are not the conceptual core of the system.
即使这种通用模型元素被视为至关重要,整体领域模型也需要突出系统中最具增值性和最特殊的方面,并且需要构建以赋予该部分尽可能多的权力。当 CORE与所有相互关联的因素混合在一起时,这很难做到。
Even if such a generic model element is deemed critical, the overall domain model needs to make prominent the most value-adding and special aspects of your system, and needs to be structured to give that part as much power as possible. This is hard to do when the CORE is mixed with all the interrelated factors.
所以:
Therefore:
识别那些不是您项目动机的内聚子域。分解出这些子域的通用模型,并将它们放在单独的模块中。不要在其中留下任何您的专长的痕迹。
Identify cohesive subdomains that are not the motivation for your project. Factor out generic models of these subdomains and place them in separate MODULES. Leave no trace of your specialties in them.
一旦将它们分开,请将其后续开发的优先级设置为低于核心域,并避免将任务分配给核心开发人员(因为他们从中不会获得太多领域知识)。还请考虑为这些通用子域提供现成的解决方案或已发布的模型。
Once they have been separated, give their continuing development lower priority than the CORE DOMAIN, and avoid assigning your core developers to the tasks (because they will gain little domain knowledge from them). Also consider off-the-shelf solutions or published models for these GENERIC SUBDOMAINS.
You may have a few extra options when developing these packages.
有时您可以购买实现或使用开源代码。
Sometimes you can buy an implementation or use open source code.
优点
Advantages
• 需要开发的代码更少。
• Less code to develop.
• 维护负担外部化。
• Maintenance burden externalized.
• 代码可能更成熟,在多个地方使用,因此比自己开发的代码更加可靠和完整。
• Code is probably more mature, used in multiple places, and therefore more bulletproof and complete than homegrown code.
缺点
Disadvantages
• 在使用它之前,您仍然需要花时间对其进行评估和了解。
• You still have to spend the time to evaluate it and understand it before using it.
• 质量控制是我们行业的一部分,您不能指望它是正确和稳定的。
• Quality control being what it is in our industry, you can’t count on it being correct and stable.
• 它可能对您的目的而言过于复杂;集成可能比最低限度的自主实施需要做更多的工作。
• It may be overengineered for your purposes; integration could be more work than a minimalist homegrown implementation.
• 外来元素通常无法顺利整合。可能存在不同的BOUNDED CONTEXT。即使没有,也可能很难顺利地从其他包中引用ENTITIES 。
• Foreign elements don’t usually integrate smoothly. There may be a distinct BOUNDED CONTEXT. Even if not, it may be difficult to smoothly reference ENTITIES from your other packages.
• 它可能会引入平台依赖性、编译器版本依赖性等等。
• It may introduce platform dependencies, compiler version dependencies, and so on.
现成的子域解决方案值得研究,但通常不值得费心。我见过一些应用程序的成功案例,这些应用程序具有非常复杂的工作流要求,它们使用了带有 API 挂钩的商用外部工作流系统。我还见过深度集成到应用程序中的错误日志记录包的成功案例。有时,通用子域解决方案以框架的形式打包,这些框架实现了一个非常抽象的模型,可以与您的应用程序集成并专门用于您的应用程序。子组件越通用,其自身模型越精炼,它就越有可能有用。
Off-the-shelf subdomain solutions are worth investigating, but they are usually not worth the trouble. I’ve seen success stories in applications with very elaborate workflow requirements that used commercially available external workflow systems with API hooks. I’ve also seen success with an error-logging package that was deeply integrated into the application. Sometimes GENERIC SUBDOMAIN solutions are packaged in the form of frameworks, which implement a very abstract model that can be integrated with and specialized for your application. The more generic the subcomponent, and the more distilled its own model, the better the chance that it will be useful.
优点
Advantages
• 比本土模型更成熟,反映了许多人的见解
• More mature than a homegrown model and reflects many people’s insights
• 即时、高质量的文档
• Instant, high-quality documentation
坏处
Disadvantage
• 可能不太符合您的需求,或者可能设计过度
• May not quite fit your needs or may be overengineered for your needs
汤姆·莱勒(Tom Lehrer,20 世纪 50 和 60 年代的喜剧歌曲作家)曾说过,数学成功的秘诀就是“剽窃!剽窃。不要让任何人的作品逃过你的眼睛……但一定要确保始终称其为‘请研究’。”这是领域建模方面的好建议,尤其是在攻击通用子域时。
Tom Lehrer (the comedic songwriter from the 1950s and 1960s) said the secret to success in mathematics was, “Plagiarize! Plagiarize. Let no one’s work evade your eyes. . . . Only be sure always to call it please, research.” Good advice in domain modeling, and especially when attacking a GENERIC SUBDOMAIN.
当存在广泛分布的模型时,这种方法最有效,例如《分析模式》(Fowler 1996)中的模型。(参见第 11 章。)
This works best when there is a widely distributed model, such as the ones in Analysis Patterns (Fowler 1996). (See Chapter 11.)
如果该领域已经有一个高度形式化和严格的模型,那就使用它。会计和物理学就是两个例子。它们不仅非常强大和精简,而且被世界各地的人们广泛理解,从而减轻了你现在和将来的训练负担。(请参阅第 10 章,了解如何使用已建立的形式主义。)
When the field already has a highly formalized and rigorous model, use it. Accounting and physics are two examples that come to mind. Not only are these very robust and streamlined, but they are widely understood by people everywhere, reducing your present and future training burden. (See Chapter 10, on using established formalisms.)
如果您能找到一个简化的子集,既自洽又能满足您的需求,就不必强制实施已发布模型的所有方面。但如果有一个广为人知且记录良好的(或者更好的是,形式化的)模型可用,那么重新发明轮子就毫无意义了。
Don’t feel compelled to implement all aspects of a published model, if you can identify a simplified subset that is self-consistent and satisfies your needs. But in cases where there is a well-traveled and well-documented—or better yet, formalized—model available, it makes no sense to reinvent the wheel.
优点
Advantages
• 让核心团队自由地从事核心领域的工作,该领域需要并积累大多数知识。
• Keeps core team free to work on the CORE DOMAIN, where most knowledge is needed and accumulated.
• 允许进行更多开发,而无需永久扩大团队,但不会分散对核心领域的知识。
• Allows more development to be done without permanently enlarging the team, but without dissipating knowledge of the CORE DOMAIN.
•强制采用面向接口的设计,并有助于保持子域的通用性,因为规范正在向外部传递。
• Forces an interface-oriented design, and helps keep the subdomain generic, because the specification is being passed outside.
缺点
Disadvantages
• 仍然需要核心团队投入时间,因为需要沟通界面、编码标准和任何其他重要方面。
• Still requires time from the core team, because the interface, coding standards, and any other important aspects need to be communicated.
• 将所有权转移回内部会产生大量开销,因为必须理解代码。(不过,开销比专门的子域要小,因为通用模型可能不需要特殊背景知识即可理解。)
• Incurs significant overhead of transferring ownership back inside, because code has to be understood. (Still, overhead is less than for specialized subdomains, because a generic model presumably requires no special background to understand.)
• 代码质量可能有所不同。这可能是好是坏,取决于两个团队的相对能力。
• Code quality can vary. This could be good or bad, depending on the relative caliber of the two teams.
自动化测试在外包中可以发挥重要作用。实施者应该被要求为他们交付的代码提供单元测试。一种真正有效的方法——有助于确保一定程度的质量、澄清规范并顺利重新集成——是为外包组件指定甚至编写自动化验收测试。此外,“外包实施”可以与“已发布的设计或模型”完美结合。
Automated tests can play an important role in outsourcing. The implementers should be required to provide unit tests for the code they deliver. A really powerful approach—one that helps ensure a degree of quality, clarifies the spec, and smooths reintegration—is to specify or even write automated acceptance tests for the outsourced components. Also, “outsourced implementation” can be an excellent combination with “published design or model.”
优点
Advantages
• 易于集成。
• Easy integration.
• 您只会得到您想要的东西,不会得到任何额外的东西。
• You get just what you want and nothing extra.
• 可以指派临时承包商。
• Temporary contractors can be assigned.
缺点
Disadvantages
• 持续的维护和培训负担。
• Ongoing maintenance and training burden.
• 人们很容易低估开发此类软件包所需的时间和成本。
• It is easy to underestimate the time and cost of developing such packages.
当然,这也与“已发布的设计或模型”很好地结合在一起。
Of course, this too combines well with “published design or model.”
通用子域名 是尝试应用外部设计专业知识的地方,因为它们不需要深入了解您的专业核心领域,并且它们不会提供学习该领域的主要机会。机密性不太受关注,因为此类模块很少涉及专有信息或商业实践。通用子域减轻了那些不致力于深入了解该领域的人的培训负担。
GENERIC SUBDOMAINS are the place to try to apply outside design expertise, because they do not require deep understanding of your specialized CORE DOMAIN, and they do not present a major opportunity to learn that domain. Confidentiality is of less concern, because little proprietary information or business practice will be involved in such modules. A GENERIC SUBDOMAIN lessens the training burden for those not committed to deep knowledge of the domain.
随着时间的推移,我相信我们对核心模型的构成概念会越来越狭隘,越来越多的通用模型将作为实施框架,或至少作为已发布的模型或分析模式提供。目前,我们仍然必须自己开发其中的大部分,但将它们从核心域模型中分离出来具有很大的价值。
Over time, I believe our ideas of what constitutes the CORE model will narrow, and more and more generic models will be available as implemented frameworks, or at least as published models or analysis patterns. For now, we still have to develop most of these ourselves, but there is great value in partitioning them from the CORE DOMAIN model.
我曾两次看到一个项目中最好的开发人员花费数周时间来解决存储和转换时区时间的问题。虽然我总是怀疑这样的活动,但有时这是必要的,这两个项目提供了几乎完美的对比。
Twice I’ve watched as the best developers on a project spent weeks of their time solving the problem of storing and converting times with time zones. While I’m always suspicious of such activities, sometimes it is necessary, and these two projects provide almost perfect contrast.
第一项是设计货运调度软件。要安排国际运输,精确计算时间至关重要,而且由于所有此类调度都是按当地时间跟踪的,因此如果不进行转换,就无法协调运输。
The first was an effort to design scheduling software for cargo shipping. To schedule international transports, it is critical to have accurate time calculations, and because all such schedules are tracked in local time, it is impossible to coordinate transports without conversions.
在明确了对这一功能的需求后,团队开始使用可用的时间类别和一些虚拟数据开发核心域和应用程序的一些早期迭代。随着应用程序开始成熟,很明显现有的时间类别不够用,而且由于许多国家之间的差异和国际日期变更线的复杂性,问题非常复杂。随着他们的需求现在更加明确,他们寻找现成的解决方案,但一无所获。他们别无选择,只能自己构建它。
Having clearly established their need for this functionality, the team proceeded with development of the CORE DOMAIN and some early iterations of the application using the available time classes and some dummy data. As the application began to mature, it was clear that the existing time classes were not adequate, and that the problem was very intricate because of the variations between the many countries and the complexity of the International Date Line. With their requirements by now even clearer, they searched for an off-the-shelf solution, but found none. They had no option but to build it themselves.
这项任务需要研究和精密工程,因此团队领导指派了他们最好的程序员之一。但这项任务不需要任何特殊的运输知识,也不会培养这方面的知识,所以他们选择了一名以临时合同参与该项目的程序员。
The task would require research and precision engineering, so the team leaders assigned one of their best programmers. But the task did not require any special knowledge of shipping and would not cultivate that knowledge, so they chose a programmer who was on the project on a temporary contract.
这位程序员并不是从零开始的。他研究了几种现有的时区实现,其中大多数都不符合要求,并决定采用 BSD Unix 的公共域解决方案,该解决方案有一个复杂的数据库和一个用 C 实现的实现。他对逻辑进行了逆向工程,并为数据库编写了一个导入例程。
This programmer did not start from scratch. He researched several existing implementations of time zones, most of which did not meet requirements, and decided to adapt the public-domain solution from BSD Unix, which had an elaborate database and an implementation in C. He reverse-engineered the logic and wrote an import routine for the database.
问题比预想的还要难(例如,涉及特殊情况数据库的导入),但代码已编写并与 CORE 集成,产品也已交付。
The problem turned out to be even harder than expected (involving, for example, the import of databases of special cases), but the code got written and integrated with the CORE and the product was delivered.
另一个项目的情况则截然不同。一家保险公司正在开发一套新的索赔处理系统,并计划记录各种事件的时间(车祸时间、冰雹时间等等)。这些数据将以当地时间记录,因此需要时区功能。
Things went very differently on the other project. An insurance company was developing a new claims-processing system, and planned to capture the times of various events (time of car crash, time of hail storm, and so on). This data would be recorded in local time, so time zone functionality was needed.
当我到达时,他们已经指派了一名初级但非常聪明的开发人员来负责这项任务,尽管应用程序的具体要求仍在制定中,甚至还没有尝试过初始迭代。他尽职尽责地着手先验地构建时区模型。
When I arrived, they had assigned a junior, but very smart, developer to the task, although the exact requirements of the app were still in play and not even an initial iteration had been attempted. He had dutifully set out to build a time zone model a priori.
由于不知道需要什么,所以假设它应该足够灵活以处理任何事情。负责这项任务的程序员需要帮助解决如此困难的问题,因此还指派了一名高级开发人员。他们编写了复杂的代码,但没有特定的应用程序使用它,因此始终不清楚代码是否正常工作。
Not knowing what would be needed, it was assumed that it should be flexible enough to handle anything. The programmer assigned to the task needed help with such a difficult problem, so a senior developer was assigned to it also. They wrote complex code, but no specific application was using it, so it was never clear that the code worked correctly.
该项目因各种原因搁浅,时区代码从未被使用。但如果真的使用,只需存储标有时区的当地时间就足够了,甚至无需转换,因为这主要是参考数据,而不是计算的基础。即使转换是必要的,所有数据都将从北美收集,那里的时区转换相对简单。
The project ran aground for various reasons, and the time zone code was never used. But if it had been, simply storing local times tagged with the time zone might have been sufficient, even with no conversion, because this was primarily reference data and not the basis of computations. Even if conversion had turned out to be necessary, all the data was going to be gathered from North America, where time zone conversions are relatively simple.
对时区的关注所带来的主要代价是忽视了核心域模型。如果把同样的精力放在这上面,他们可能已经制作出了一个他们自己的应用程序的功能原型和初版工作领域模型。此外,参与该项目的开发人员应该对保险领域有深入的了解,从而为团队积累关键知识。
The main cost of this attention to the time zones was the neglect of the CORE DOMAIN model. If the same energy had been placed there, they might have produced a functioning prototype of their own application and a first cut at a working domain model. Furthermore, the developers involved, who were committed long-term to the project, should have been steeped in the insurance domain, building up critical knowledge within the team.
这两个项目都做对了一件事,那就是将通用时区模型与核心域完全分开。航运专用或保险专用的时区模型会将该模型与这个通用支持模型耦合在一起,使核心更难理解(因为它会包含与时区无关的细节)。这会使时区模块更难维护(因为维护者必须了解核心及其与时区的相互关系)。
One thing both projects did right was to cleanly segregate the GENERIC time zone model from the CORE DOMAIN. A shipping-specific or insurance-specific model of time zones would have coupled the model to this generic supporting model, making the CORE harder to understand (because it would contain irrelevant detail about time zones). It would have made the time zone MODULE harder to maintain (because the maintainer would have to understand the CORE and its interrelationship with time zones).
我们这些技术人员往往喜欢解决时区转换等可定义的问题,我们很容易证明花时间解决这些问题是合理的。但从严谨的角度看,优先事项通常指向核心领域。
We technical people tend to enjoy definable problems like time zone conversion, and we can easily justify spending our time on them. But a disciplined look at priorities usually points to the CORE DOMAIN.
请注意,虽然我强调了这些子域的通用性,但我没有提到代码的可重用性。现成的解决方案可能对特定情况有意义,也可能没有意义,但假设您自己(内部或外包)实施代码,您不应该特别关心代码的可重用性。这违背了提炼的基本动机:您应该将尽可能多的精力投入到核心域,并仅在必要时投资支持通用子域。
Note that while I have emphasized the generic quality of these subdomains, I have not mentioned the reusability of code. Off-the-shelf solutions may or may not make sense for a particular situation, but assuming that you are implementing the code yourself, in-house or outsourced, you should specifically not concern yourself with the reusability of that code. This would go against the basic motivation of distillation: that you should be applying as much of your effort to the CORE DOMAIN as possible and investing in supporting GENERIC SUB-DOMAINS only as necessary.
重用确实会发生,但并不总是代码重用。模型重用通常是更高级别的重用,例如当您使用已发布的设计或模型时。如果您必须创建自己的模型,那么它可能会在以后的相关项目中很有价值。但是,虽然这种模型的概念可能适用于许多情况,但您不必完全通用地开发模型。您可以只对业务所需的部分进行建模和实施。
Reuse does happen, but not always code reuse. The model reuse is often a better level of reuse, as when you use a published design or model. And if you have to create your own model, it may well be valuable in a later related project. But while the concept of such a model may be applicable to many situations, you do not have to develop the model in its full generality. You can model and implement only the part you need for your business.
尽管您很少为可重用性进行设计,但您必须严格遵守通用概念。引入行业特定的模型元素将有两个代价。首先,它会阻碍未来的发展。虽然您现在只需要子域模型的一小部分,但您的需求会增长。通过向设计中引入任何不属于概念的东西,您将很难干净地扩展系统,而无需完全重建旧部分并重新设计使用它的其他模块。
Though you should seldom design for reusability, you must be strict about keeping within the generic concept. Introducing industry-specific model elements will have two costs. First, it will impede future development. Although you need only a small part of the subdomain model now, your needs will grow. By introducing anything to the design that is not part of the concept, you make it much more difficult to expand the system cleanly without completely rebuilding the older part and redesigning the other modules that use it.
第二个也是更重要的原因是,这些行业特定概念要么属于核心领域,要么属于它们自己更专业的子领域,而这些专业模型甚至比通用模型更有价值。
The second, and more important, reason is that those industry-specific concepts belong either in the CORE DOMAIN or in their own, more specialized, subdomains, and those specialized models are even more valuable than the generic ones.
敏捷流程通常要求通过尽早处理风险最高的任务来管理风险。XP 特别要求立即启动并运行端到端系统。这个初始系统通常证明了一种技术架构,并且很容易构建一个处理一些支持通用子域的外围系统,因为这些通常更容易分析。但要小心;这可能会违背风险管理的目的。
Agile processes typically call for managing risk by tackling the riskiest tasks early. XP specifically calls for getting an end-to-end system up and running immediately. This initial system often proves a technical architecture, and it is tempting to build a peripheral system that handles some supporting GENERIC SUBDOMAIN because these are usually easier to analyze. But be careful; this can defeat the purpose of risk management.
项目面临来自两方面的风险,有些项目具有较大的技术风险,而另一些项目具有较大的领域建模风险。端到端系统仅在其是实际系统中具有挑战性的部分的雏形版本的情况下才可以减轻风险。人们很容易低估领域建模风险。它可以表现为不可预见的复杂性、无法充分接触业务专家或开发人员的关键技能存在差距。
Projects face risk from both sides, with some projects having greater technical risks and others greater domain modeling risks. The end-to-end system mitigates risk only to the extent that it is an embryonic version of the challenging parts of the actual system. It is easy to underestimate the domain modeling risk. It can take the form of unforeseen complexity, inadequate access to business experts, or gaps in key skills of the developers.
因此,除非团队拥有可证明的技能并且对该领域非常熟悉,否则初步确定的系统应该基于核心领域的某些部分,无论多么简单。
Therefore, except when the team has proven skills and the domain is very familiar, the first-cut system should be based on some part of the CORE DOMAIN, however simple.
同样的原则适用于任何试图推动高风险任务向前发展的流程:核心领域之所以具有高风险,是因为它往往出乎意料地困难,而且没有它,项目就无法成功。
The same principle applies to any process that tries to push high-risk tasks forward: the CORE DOMAIN is high risk because it is often unexpectedly difficult and because without it, the project cannot succeed.
本章中的大多数提炼模式都展示了如何更改模型和代码以提炼出核心领域。然而,接下来的两个模式,领域愿景声明和重点核心,展示了如何使用补充文档,以很少的投资,改善对核心的沟通和认识,并集中开发工作。...
Most of the distillation patterns in this chapter show how to change the model and code to distill the CORE DOMAIN. However, the next two patterns, DOMAIN VISION STATEMENT and HIGHLIGHTED CORE, show how the use of supplemental documents can, with a very minor investment, improve communication and awareness of the CORE and focus development effort. . . .
在项目开始时,模型通常还不存在,但重点开发模型的需求已经存在。在开发的后期阶段,需要解释系统的价值,而不需要深入研究模型。此外,领域模型的关键方面可能跨越多个有界上下文,但根据定义,这些不同的模型无法构建以显示它们的共同重点。
At the beginning of a project, the model usually doesn’t even exist, yet the need to focus its development is already there. In later stages of development, there is a need for an explanation of the value of the system that does not require an in-depth study of the model. Also, the critical aspects of the domain model may span multiple BOUNDED CONTEXTS, but by definition these distinct models can’t be structured to show their common focus.
许多项目团队为管理层撰写“愿景声明”。这些文档中最好的部分列出了应用程序将为组织带来的具体价值。有些文档提到创建领域模型是一项战略资产。通常,愿景声明文档在项目获得资金后就被抛弃了,它从未在实际开发过程中使用过,甚至从未被技术人员阅读过。
Many project teams write “vision statements” for management. The best of these documents lay out the specific value the application will bring to the organization. Some mention the creation of the domain model as a strategic asset. Usually the vision statement document is abandoned after the project gets funding, and it is never used in the actual development process or even read by the technical staff.
领域愿景声明模仿此类文档,但重点在于领域模型的性质以及它对企业的价值。管理和技术人员可以在开发的所有阶段直接使用它来指导资源分配、指导建模选择以及教育团队成员。如果领域模型服务于多位大师,那么此文档可以展示如何平衡他们的利益。
A DOMAIN VISION STATEMENT is modeled after such documents, but it focuses on the nature of the domain model and how it is valuable to the enterprise. It can be used directly by the management and technical staff during all phases of development to guide resource allocation, to guide modeling choices, and to educate team members. If the domain model serves many masters, this document can show how their interests are balanced.
所以:
Therefore:
写一个简短的描述(大约一页)来描述核心领域及其将带来的价值,即“价值主张”。忽略那些没有将此领域模型与其他领域模型区分开来的方面。展示领域模型如何服务和平衡不同的利益。保持范围狭窄。尽早写下这个陈述,并在获得新的见解时对其进行修改。
Write a short description (about one page) of the CORE DOMAIN and the value it will bring, the “value proposition.” Ignore those aspects that do not distinguish this domain model from others. Show how the domain model serves and balances diverse interests. Keep it narrow. Write this statement early and revise it as you gain new insight.
领域愿景声明可以用作指导方针,使开发团队在不断提炼模型和代码的过程中朝着共同的方向前进。它可以与非技术团队成员、管理层甚至客户共享(当然,除非其中包含专有信息)。
A DOMAIN VISION STATEMENT can be used as a guidepost that keeps the development team headed in a common direction in the ongoing process of distilling the model and code itself. It can be shared with nontechnical team members, management, and even customers (except where it contains proprietary information, of course).
领域愿景声明 为团队提供共同的方向。通常需要在高级STATEMENT和代码或模型的完整细节之间建立某种桥梁。...
A DOMAIN VISION STATEMENT gives the team a shared direction. Some bridge between the high-level STATEMENT and the full detail of the code or model will usually be needed. . . .
领域愿景声明从广义上确定了核心领域,但具体的核心模型元素的确定则留给了个人的解读。除非团队的沟通水平极高,否则愿景声明本身不会产生太大影响。
A DOMAIN VISION STATEMENT identifies the CORE DOMAIN in broad terms, but it leaves the identification of the specific CORE model elements up to the vagaries of individual interpretation. Unless there is an exceptionally high level of communication on the team, the VISION STATEMENT alone will have little impact.
尽管团队成员可能大致了解核心域的构成,但不同的人挑选出的元素不会完全相同,甚至同一个人一天到晚挑选出的关键部分也不会始终如一。不断筛选模型以识别关键部分的脑力劳动会占用原本应该用于设计思考的注意力,并且需要对模型有全面的了解。必须让核心域更容易被看到。
Even though team members may know broadly what constitutes the CORE DOMAIN, different people won’t pick out quite the same elements, and even the same person won’t be consistent from one day to the next. The mental labor of constantly filtering the model to identify the key parts absorbs concentration better spent on design thinking, and it requires comprehensive knowledge of the model. The CORE DOMAIN must be made easier to see.
对代码进行重大的结构性更改是识别核心域的理想方式,但在短期内并不总是可行的。事实上,如果没有团队所缺乏的观点,这种重大的代码更改很难进行。
Significant structural changes to the code are the ideal way of identifying the CORE DOMAIN, but they are not always practical in the short term. In fact, such major code changes are difficult to undertake without the very view the team is lacking.
模型组织中的结构变化(例如划分通用子域和本章后面介绍的其他几个子域)可以让模块讲述故事。但作为传达核心域的唯一方式,这太过雄心勃勃,无法立即实现。
Structural changes in the organization of the model, such as partitioning GENERIC SUBDOMAINS and a few others to come later in this chapter, can allow the MODULES to tell the story. But as the only means of communicating the CORE DOMAIN, this is too ambitious to shoot for straight away.
您可能需要一个更轻量级的解决方案来补充这些激进的技术。您可能有限制,无法物理分离 CORE 。或者您可能从无法很好地区分CORE的现有代码开始,但您确实需要看到CORE并分享该视图,以便有效地重构以实现更好的提炼。即使在高级阶段,一些精心挑选的图表或文档也可以为团队提供心理锚点和切入点。
You will probably need a lighter solution to supplement these aggressive techniques. You may have constraints that prevent you from physically separating the CORE. Or you may be starting out with existing code that does not differentiate the CORE well, but you really need to see the CORE, and share that view, to effectively refactor toward better distillation. And even at an advanced stage, a few carefully selected diagrams or documents provide mental anchor points and entry points for the team.
这些问题同样适用于使用复杂 UML 模型的项目和那些只保留少量外部文档并使用代码作为模型主要存储库的项目(如 XP 项目)。极限编程团队可能更简约,让这些补充更随意、更短暂(例如,虽然这种方法可能并不像人们想象的那么复杂(墙上挂着一张手绘的图表,供所有人查看),但这些技术可以很好地融入到整个过程中。
These issues arise equally for projects that use elaborate UML models and those (such as XP projects) that keep few external documents and use the code as the primary repository of the model. An Extreme Programming team might be more minimalist, keeping these supplements more casual and more transient (for example, a hand-drawn diagram on the wall for all to see), but these techniques can fold nicely into the process.
标记模型的特权部分以及体现它的实现是对模型的反映,不一定是模型本身的一部分。任何能让每个人都轻松了解核心域的技术都可以。两种特定技术可以代表此类解决方案。
Marking off a privileged part of a model, along with the implementation that embodies it, is a reflection on the model, not necessarily part of the model itself. Any technique that makes it easy for everyone to know the CORE DOMAIN will do. Two specific techniques can represent this class of solutions.
我经常会创建一个单独的文档来描述和解释核心领域。它可以简单到只是列出最重要的概念对象。它可以是一组关注这些对象的图表,展示它们之间最关键的关系。它可以从抽象层面或通过示例介绍基本交互。它可以使用 UML 类图或序列图、特定于领域的非标准图、措辞谨慎的文本解释或这些的组合。提炼文档不是完整的设计文档。它是一个极简的切入点,描述和解释了核心,并提出了仔细研究特定部分的原因。读者可以大致了解各个部分是如何组合的,并可以引导到代码的适当部分以了解更多详细信息。
Often I create a separate document to describe and explain the CORE DOMAIN. It can be as simple as a list of the most essential conceptual objects. It can be a set of diagrams focused on those objects, showing their most critical relationships. It can walk through the fundamental interactions at an abstract level or by example. It can use UML class or sequence diagrams, nonstandard diagrams particular to the domain, carefully worded textual explanations, or combinations of these. A distillation document is not a complete design document. It is a minimalist entry point that delineates and explains the CORE and suggests reasons for closer scrutiny of particular pieces. The reader is given a broad view of how the pieces fit and guided to the appropriate part of the code for more details.
因此(作为HIGHLIGHTED CORE的一种形式):
Therefore (as one form of HIGHLIGHTED CORE):
编写一份非常简短的文档(三到七页),描述核心域和核心元素之间的主要交互。
Write a very brief document (three to seven sparse pages) that describes the CORE DOMAIN and the primary interactions among CORE elements.
所有单独文件通常的风险均适用。
All the usual risks of separate documents apply.
1.该文件可能无法被维护。
1. The document may not be maintained.
2.该文件可能无法被读取。
2. The document may not be read.
3.通过增加信息源,该文件可能会违背其消除复杂性的目的。
3. By multiplying the information sources, the document may defeat its own purpose of cutting through complexity.
限制这些风险的最佳方法是绝对简约。远离平凡的细节,专注于核心抽象及其相互作用,可以让文档老化得更慢,因为这个级别的模型通常更稳定。
The best way to limit these risks is to be absolutely minimalist. Staying away from mundane detail and focusing on the central abstractions and their interactions allows the document to age more slowly, because this level of the model is usually more stable.
编写文档,以便团队中非技术成员能够理解。将其用作共享视图,描述每个人需要了解的内容。每个人都需要知道,并且所有团队成员都可以通过它开始探索模型和代码。
Write the document to be understood by the nontechnical members of the team. Use it as a shared view that delineates what every-one needs to know, and a guide by which all team members may start their exploration of the model and code.
在一家大型保险公司的项目工作的第一天,我收到了一份“领域模型”的副本,这是一份长达 200 页的文档,是从一个行业联盟花费巨资购买的。我花了几天时间仔细阅读了一大堆类图,内容涵盖了从保险单的详细构成到人际关系的极其抽象的模型等所有内容。这些模型的分解质量从高中项目到相当好(一些模型甚至描述了业务规则,至少在随附的文本中)。但是从哪里开始呢?200 页。
On my first day on a project at a major insurance company, I was given a copy of the “domain model,” a two-hundred-page document, purchased at great expense from an industry consortium. I spent a few days wading through a jumble of class diagrams covering everything from the detailed composition of insurance policies to extremely abstract models of relationships between people. The quality of the factoring of these models ranged from high-school project to rather good (a few even described business rules, at least in the accompanying text). But where to start? Two hundred pages.
项目文化非常倾向于抽象框架的构建,而我的前任则专注于人与人、人与事物以及人与活动或协议之间关系的非常抽象的模型。这实际上是对这些关系的一个很好的分析,他们对该模型的实验具有学术研究项目的质量。但它并没有让我们接近保险应用。
The project culture heavily favored abstract framework building, and my predecessors had focused on a very abstract model of the relationship of people with each other, with things, and with activities or agreements. It was actually a nice analysis of these relationships, and their experiments with the model had the quality of an academic research project. But it wasn’t getting us anywhere near an insurance application.
我的第一反应是开始削减开支,找到一个小的核心领域作为后盾,然后对其进行重构,并在过程中重新引入其他复杂性。但管理层对这种态度感到震惊。这份文件被赋予了极大的权威。它的制作涉及了整个行业的专家,无论如何,他们付给财团的钱比付给我的钱多得多,所以他们不太可能过分重视我提出的彻底改革建议。但我知道我们必须对我们的核心领域有一个共同的认识,并让每个人的努力都集中在这一点上。
My first instinct was to start slashing, finding a small CORE DOMAIN to fall back on, then refactoring that and reintroducing other complexities as we went. But the management was alarmed by this attitude. The document was invested with great authority. Its production had involved experts from across the industry, and in any event they had paid the consortium far more than they were paying me, so they were unlikely to weigh my recommendations for radical change too heavily. But I knew we had to get a shared picture of our CORE DOMAIN and get everyone’s efforts focused on that.
我没有进行重构,而是浏览了文档,并在一位对保险行业和我们要构建的应用程序的具体要求非常了解的业务分析师的帮助下,确定了几个部分,这些部分介绍了我们需要处理的基本、差异化概念。我提供了模型导航,清楚地显示了核心及其与支持功能的关系。
Instead of refactoring, I went through the document and, with the help of a business analyst who knew a great deal about the insurance industry in general and the requirements of the application we were to build in particular, I identified the handful of sections that presented the essential, differentiating concepts we needed to work with. I provided a navigation of the model that clearly showed the CORE and its relationship to supporting features.
新的原型设计工作从这个角度开始,并很快产生了一个简化的应用程序,演示了一些所需的功能。
A new prototyping effort started from this perspective, and quickly yielded a simplified application that demonstrated some of the required functionality.
几页标签和一些黄色荧光笔将两磅可回收纸变成了商业资产。
Two pounds of recyclable paper was turned into a business asset by a few page tabs and some yellow highlighter.
这种技术并不特定于纸面上的对象图。大量使用 UML 图的团队可以使用“原型”来识别核心元素。使用代码作为模型唯一存储库的团队可能会使用注释(可能以 Java Doc 的形式构建),或者在其开发环境中使用某种工具。具体技术并不重要,只要开发人员能够毫不费力地看到核心域中的内容和外在的内容即可。
This technique is not specific to object diagrams on paper. A team that uses UML diagrams extensively could use a “stereotype” to identify core elements. A team that uses the code as the sole repository of the model might use comments, maybe structured as Java Doc, or might use some tool in its development environment. The particular technique doesn’t matter, as long as a developer can effortlessly see what is in and what is out of the CORE DOMAIN.
因此(作为HIGHLIGHTED CORE的另一种形式):
Therefore (as another form of HIGHLIGHTED CORE):
在模型的主存储库中标记CORE DOMAIN的每个元素,而无需特别阐明其作用。让开发人员轻松知道哪些内容在 CORE 之内,哪些内容不在 CORE 之内。
Flag each element of the CORE DOMAIN within the primary repository of the model, without particularly trying to elucidate its role. Make it effortless for a developer to know what is in or out of the CORE.
现在,对于那些使用该模型的人来说,核心域已经清晰可见,只需付出相当少的努力和较低的维护成本,至少在模型分解得足够精细以区分各部分的贡献的范围内。
The CORE DOMAIN is now clearly visible to those working with the model, with a fairly small effort and low maintenance, at least to the extent that the model is factored fine enough to distinguish the contributions of parts.
理论上,在 XP 项目中,任何一对程序员(两个程序员一起工作)都可以更改系统中的任何代码。实际上,一些更改会产生重大影响,需要更多的协商和协调。在基础设施层工作时,更改的影响可能很明显,但在领域层,更改的影响可能不那么明显,因为通常组织方式是这样的。
Theoretically on an XP project, any pair (two programmers working together) can change any code in the system. In practice, some changes have major implications, and call for more consultation and coordination. When working in the infrastructure layer, the impact of a change may be clear, but it may not be so obvious in the domain layer, as typically organized.
有了CORE DOMAIN的概念,这种影响就变得清晰起来。对CORE DOMAIN模型的更改应该会产生很大的影响。对广泛使用的通用元素的更改可能需要大量代码更新,但它们仍然不应该像CORE更改那样造成概念上的转变。
With the concept of the CORE DOMAIN, this impact can be made clear. Changes to the model of the CORE DOMAIN should have a big effect. Changes to widely used generic elements may require a lot of code updating, but they still shouldn’t create the conceptual shift that CORE changes do.
使用蒸馏文档作为指南。当开发人员意识到蒸馏文档本身需要更改才能与其代码或模型更改保持同步时,就需要进行协商。他们要么从根本上改变核心域元素,要么关系,或者他们正在改变CORE的边界,包括或排除一些不同的东西。无论团队使用什么沟通渠道,都需要将模型变更传播给整个团队,包括分发新版本的提炼文档。
Use the distillation document as a guide. When developers realize that the distillation document itself requires change to stay in sync with their code or model change, then consultation is called for. Either they are fundamentally changing the CORE DOMAIN elements or relationships, or they are changing the boundaries of the CORE, including or excluding something different. Dissemination of the model change to the whole team is necessary by whatever communication channels the team uses, including distribution of a new version of the distillation document.
如果提炼文档概述了CORE DOMAIN的要点,那么它就可以作为模型更改重要性的实际指标。当模型或代码更改影响到提炼文档时,需要与其他团队成员协商。进行更改时,需要立即通知所有团队成员,并传播文档的新版本。CORE 之外的更改或提炼文档中未包含的细节的更改可以在无需协商或通知的情况下集成,其他成员将在工作过程中遇到这些更改。然后开发人员就可以拥有 XP 所建议的完全自主权。
If the distillation document outlines the essentials of the CORE DOMAIN, then it serves as a practical indicator of the significance of a model change. When a model or code change affects the distillation document, it requires consultation with other team members. When the change is made, it requires immediate notification of all team members, and the dissemination of a new version of the document. Changes outside the CORE or to details not included in the distillation document can be integrated without consultation or notification and will be encountered by other members in the course of their work. Then the developers have the full autonomy that XP suggests.
尽管VISION STATEMENT和HIGHLIGHTED CORE提供了信息和指导,但它们实际上并没有修改模型或代码本身。对GENERIC SUBDOMAINS进行分区会从物理上消除一些分散注意力的元素。接下来的模式将研究如何从结构上改变模型和设计本身,以使CORE DOMAIN更加可见和易于管理。...
Although the VISION STATEMENT and HIGHLIGHTED CORE inform and guide, they do not actually modify the model or the code itself. Partitioning GENERIC SUBDOMAINS physically removes some distracting elements. The next patterns look at ways to structurally change the model and the design itself to make the CORE DOMAIN more visible and manageable. . . .
封装机制是面向对象设计的标准原则。将复杂的算法隐藏在具有意图揭示名称的方法中,将“是什么”与“如何”区分开来。这种技术使设计更易于理解和使用。但它遇到了自然的限制。
Encapsulating mechanisms is a standard principle of object-oriented design. Hiding complex algorithms in methods with intention-revealing names separates the “what” from the “how.” This technique makes a design simpler to understand and use. Yet it runs into natural limits.
计算有时会达到一定的复杂程度,导致设计变得臃肿。概念上的“是什么”被机械的“如何”淹没。大量提供解决问题的算法的方法掩盖了表达问题的方法。
Computations sometimes reach a level of complexity that begins to bloat the design. The conceptual “what” is swamped by the mechanistic “how.” A large number of methods that provide algorithms for resolving the problem obscure the methods that express the problem.
程序的激增是模型存在问题的征兆。重构以获得更深入的洞察力可以产生一个模型和设计,其元素更适合解决问题。要寻求的第一个解决方案是使计算机制变得简单的模型。但时不时地会发现,机制的某些部分本身在概念上是连贯的。这种概念计算可能不会包括您需要的所有混乱计算。我们谈论的不是某种包罗万象的“计算器”。但提取连贯的部分应该会使剩余的机制更容易理解。
This proliferation of procedures is a symptom of a problem in the model. Refactoring toward deeper insight can yield a model and design whose elements are better suited to solving the problem. The first solution to seek is a model that makes the computation mechanism simple. But now and then the insight emerges that some part of the mechanism is itself conceptually coherent. This conceptual computation will probably not include all of the messy computations you need. We are not talking about some kind of catch-all “calculator.” But extracting the coherent part should make the remaining mechanism easier to understand.
所以:
Therefore:
将概念上具有凝聚力的机制划分为单独的轻量级框架。特别注意形式主义或有据可查的算法类别。使用意图显示界面展示框架的功能。现在,该领域的其他元素可以专注于表达问题(“什么”),将解决方案的复杂性(“如何”)委托给框架。
Partition a conceptually COHESIVE MECHANISM into a separate lightweight framework. Particularly watch for formalisms or well-documented categories of algorithms. Expose the capabilities of the framework with an INTENTION-REVEALING INTERFACE. Now the other elements of the domain can focus on expressing the problem (“what”), delegating the intricacies of the solution (“how”) to the framework.
然后将这些分离的机制置于其支持角色中,留下一个更小、更具表现力的核心域,该核心域以更具声明性的方式通过界面使用该机制。
These separated mechanisms are then placed in their supporting roles, leaving a smaller, more expressive CORE DOMAIN that uses the mechanism through the interface in a more declarative style.
识别标准算法或形式主义可将设计的一些复杂性转移到一组经过研究的概念中。有了这样的指南,我们可以自信地实施解决方案,而无需反复试验。我们可以指望其他开发人员了解它,或者至少能够查找它。这类似于已发布的通用子域的好处 模型,但文档化的算法或形式化计算可能更常见,因为这一级别的计算机科学研究得更多。不过,您往往必须创建新的东西。使其专注于计算,避免混入表达域模型。职责分离:核心域或通用子域的模型制定事实、规则或问题。内聚机制根据模型指定解决规则或完成计算。
Recognizing a standard algorithm or formalism moves some of the complexity of the design into a studied set of concepts. With such a guide, we can implement a solution with confidence and little trial and error. We can count on other developers knowing about it or at least being able to look it up. This is similar to the benefits of a published GENERIC SUBDOMAIN model, but a documented algorithm or formal computation may be found more often because this level of computer science has been studied more. Still, more often than not you will have to create something new. Make it narrowly focused on the computation and avoid mixing in the expressive domain model. There is a separation of responsibilities: The model of the CORE DOMAIN or a GENERIC SUBDOMAIN formulates a fact, rule, or problem. A COHESIVE MECHANISM resolves the rule or completes the computation as specified by the model.
我在一个需要相当复杂的组织结构图模型的项目中经历了这个过程。该模型表示一个人为另一个人工作以及在组织的哪些分支机构工作,并提供了一个可以提出和回答相关问题的界面。因为大多数这些问题都是“在这个指挥系统中,谁有权力批准这个?”或“这个部门的谁有能力处理这样的问题?”,所以团队意识到大部分复杂性涉及遍历组织树的特定分支,搜索特定的人或关系。这正是由完善的图形形式化系统解决的那种问题,图形是一组由弧(称为边)连接的节点,以及遍历图形所需的规则和算法。
I went through this process on a project that needed a fairly elaborate model of an organization chart. This model represented the fact that one person worked for another, and in which branches of the organization, and it provided an interface by which relevant questions might be asked and answered. Because most of these questions were along the lines of “Who, in this chain of command, has authority to approve this?” or “Who, in this department, is capable of handling an issue like this?” the team realized that most of the complexity involved traversing specific branches of the organizational tree, searching for specific people or relationships. This is exactly the kind of problem solved by the well-developed formalism of a graph, a set of nodes connected by arcs (called edges) and the rules and algorithms needed to traverse the graph.
分包商实施了一个图形遍历框架作为内聚机制。该框架使用了大多数计算机科学家熟悉的标准图形术语和算法,并在教科书中有大量记录。他绝没有实现完全通用的图形。它是该概念框架的一个子集,涵盖了我们的组织模型所需的功能。而有了意图揭示界面,获得答案的方式就不再是主要关注点了。
A subcontractor implemented a graph traversal framework as a COHESIVE MECHANISM. This framework used standard graph terminology and algorithms familiar to most computer scientists and abundantly documented in textbooks. By no means did he implement a fully general graph. It was a subset of that conceptual framework that covered the features needed for our organization model. And with an INTENTION-REVEALING INTERFACE, the means by which the answers are obtained are not a primary concern.
现在,组织模型可以简单地使用标准图形术语来陈述,每个人都是一个节点,而每个人之间的关系都是连接这些节点的边(弧)。之后据推测,图形框架内的机制可以找到任何两个人之间的关系。
Now the organization model could simply state, using standard graph terminology, that each person is a node, and that each relationship between people is an edge (arc) connecting those nodes. After that, presumably, mechanisms within the graph framework could find the relationship between any two people.
如果将这种机制纳入领域模型,我们将面临两方面的损失。首先,该模型将与解决问题的特定方法相结合,从而限制未来的选择。更重要的是,组织模型将变得非常复杂和混乱。将机制和模型分开,可以采用更清晰的声明式风格来描述组织。而用于图形操作的复杂代码则被隔离在纯机械框架中,该框架基于经过验证的算法,可以单独维护和单元测试。
If this mechanism had been incorporated into the domain model, it would have cost us in two ways. The model would have been coupled to a particular method of solving the problem, limiting future options. More important, the model of an organization would have been greatly complicated and muddied. Keeping mechanism and model separate allowed a declarative style of describing organizations that was much clearer. And the intricate code for graph manipulation was isolated in a purely mechanistic framework, based on proven algorithms, that could be maintained and unit-tested in isolation.
内聚机制的另一个例子是用于构造SPECIFICATION对象并支持它们所需的基本比较和组合操作的框架。通过采用这样的框架,核心域和通用子域可以用该模式中描述的清晰、易懂的语言声明其SPECIFICATION (参见第 10 章)。比较和组合所涉及的复杂操作可以留给框架。
Another example of a COHESIVE MECHANISM would be a framework for constructing SPECIFICATION objects and supporting the basic comparison and combination operations expected of them. By employing such a framework, the CORE DOMAIN and GENERIC SUBDOMAINS can declare their SPECIFICATIONS in the clear, easily understood language described in that pattern (see Chapter 10). The intricate operations involved in carrying out the comparisons and combinations can be left to the framework.
通用子域和内聚机制都出于同样的愿望,即减轻核心域的负担。不同之处在于所承担的责任的性质。通用子域基于表达模型,该模型代表了团队对域的看法。在这方面,它与核心域没有什么不同,只是不那么核心、不那么重要、不那么专业。内聚机制并不代表域;它解决了表达模型带来的一些棘手的计算问题。
Both GENERIC SUBDOMAINS and COHESIVE MECHANISMS are motivated by the same desire to unburden the CORE DOMAIN. The difference is the nature of the responsibility taken on. A GENERIC SUBDOMAIN is based on an expressive model that represents some aspect of how the team views the domain. In this it is no different than the CORE DOMAIN, just less central, less important, less specialized. A COHESIVE MECHANISM does not represent the domain; it solves some sticky computational problem posed by the expressive models.
一个模型提出建议;一个凝聚机制解决。
A model proposes; a COHESIVE MECHANISM disposes.
在实践中,除非你认识到形式化的、已发布的计算,否则这种区别通常并不纯粹,至少一开始不是。在连续的重构中,它可以被提炼成更纯粹的机制或者转变为一个具有一些以前无法识别的模型概念的通用子域,这将使机制变得简单。
In practice, unless you recognize a formalized, published computation, this distinction is usually not pure, at least not at first. In successive refactoring it could either be distilled into a purer mechanism or be transformed into a GENERIC SUBDOMAIN with some previously unrecognized model concepts that would make the mechanism simple.
您几乎总是希望将机制从核心领域中移除。唯一的例外是当机制本身是专有的并且是软件价值的关键部分时。高度专业化的算法有时就是这种情况。例如,如果航运物流应用程序的显着特征之一是制定时间表的特别有效的算法,那么该机制可被视为概念核心的一部分。我曾经在一家投资银行工作过一个项目,其中用于评估风险的高度专有算法肯定属于核心领域。(事实上,它们被严格保密,甚至大多数核心开发人员都不允许看到它们。)当然,这些算法可能是真正预测风险的一组规则的特定实现。更深入的分析可能会产生一个更深层次的模型,该模型将允许这些规则明确,并具有封装的求解机制。
You almost always want to remove MECHANISMS from the CORE DOMAIN. The one exception is when a MECHANISM is itself proprietary and a key part of the value of the software. This is sometimes the case with highly specialized algorithms. For example, if one of the distinguishing features of a shipping logistics application were a particularly effective algorithm for working out schedules, that MECHANISM could be considered part of the conceptual CORE. I once worked on a project at an investment bank in which highly proprietary algorithms for rating risk were definitely in the CORE DOMAIN. (In fact, they were held so closely that even most of the CORE developers were not allowed to see them.) Of course, these algorithms are probably a particular implementation of a set of rules that really predict risk. Deeper analysis might lead to a deeper model that would allow those rules to be explicit, with an encapsulated solving mechanism.
但那将是设计中的另一个渐进式改进,留待以后再说。是否采取下一步行动的决定将基于成本效益分析:制定新设计有多难?当前设计有多难理解和修改?对于预计要从事这项工作的人来说,采用更先进的设计会容易多少?当然,有谁知道新模型会采用什么形式吗?
But that would be another incremental improvement in the design, for another day. The decision as to whether to go that next step would be based on a cost-benefit analysis: How difficult would it be to work out that new design? How difficult is the current design to understand and modify? How much easier would it be with a more advanced design, for the type of people who would be expected to do the work? And of course, does anyone have any idea what form the new model might take?
实际上,在我们完成上例中的组织模型一年后,其他开发人员重新设计了它,以消除图形框架的分离。他们认为增加的对象数量和将 MECHANISM 分离到单独包中的复杂性是没有必要的。相反,他们将节点行为添加到组织ENTITIES的父类中。尽管如此,他们保留了组织模型的声明性公共接口。他们甚至将MECHANISM封装在组织ENTITIES中。
Actually, a year after we completed the organization model in the previous example, other developers redesigned it to eliminate the separation of the graph framework. They felt the increased object count and the complication of separating the MECHANISM into a separate package were not warranted. Instead, they added node behavior to the parent class of the organizational ENTITIES. Still, they retained the declarative public interface of the organization model. They even kept the MECHANISM encapsulated, within the organizational ENTITIES.
这些完整的循环很常见,但它们不会回到起点。最终结果通常是一个更深层次的模型,可以更清楚地区分事实、目标和机制。务实的重构保留了中间阶段的重要优点,同时摆脱了不必要的复杂性。
These full circles are common, but they do not return to their starting point. The end result is usually a deeper model that more clearly differentiates facts, goals, and MECHANISMS. Pragmatic refactoring retains the important virtues of the intermediate stages while shedding the unneeded complications.
声明式设计和“声明式风格”是第 10 章的主题,但该设计风格值得在本章战略提炼中特别提及。提炼的价值在于能够看到自己在做什么:切中要害,而不会被无关紧要的细节分散注意力。当支持设计提供了一种经济的语言来表达核心的概念和规则,同时封装计算或执行这些概念和规则的手段时,核心领域的重要部分可能能够遵循声明式风格。
Declarative design and “declarative style” is a topic of Chapter 10, but that design style deserves special mention in this chapter on strategic distillation. The value of distillation is being able to see what you are doing: cutting to the essence without being distracted by irrelevant detail. Important parts of the CORE DOMAIN may be able to follow a declarative style, when the supporting design provides an economical language for expressing the concepts and rules of the CORE while encapsulating the means of computing or enforcing them.
迄今为止,当内聚机制通过具有概念连贯性断言和无副作用函数的意图显示接口提供访问时,它们是最有用的。机制和灵活的设计允许核心域做出有意义的陈述,而不是调用晦涩难懂的函数。但是,当核心域本身的一部分突破到深层模型并开始作为一种可以灵活而简洁地表达最重要的应用场景的语言时,就会产生非凡的回报。
COHESIVE MECHANISMS are by far most useful when they provide access through an INTENTION-REVEALING INTERFACE, with conceptually coherent ASSERTIONS and SIDE-EFFECT-FREE FUNCTIONS. MECHANISMS and supple designs allow the CORE DOMAIN to make meaningful statements rather than calling obscure functions. But an exceptional payoff comes when part of the CORE DOMAIN itself breaks through to a deep model and starts to function as a language that can express the most important application scenarios flexibly and concisely.
深度模型通常带有相应的柔性设计。当柔性设计成熟时,它会提供一组易于理解的元素,这些元素可以清晰地组合起来以完成复杂的任务或表达复杂的信息,就像单词组合成句子一样。此时,客户端代码将采用声明式风格,并且可以更加精炼。
A deep model often comes with a corresponding supple design. When a supple design reaches maturity, it provides an easily understood set of elements that can be combined unambiguously to accomplish complex tasks or express complex information, just as words are combined into sentences. At that point, client code takes on a declarative style and can be much more distilled.
分解出通用子域可以减少混乱,并且凝聚机制 用于封装复杂的操作。这留下了一个更集中的模型,干扰更少,不会给用户开展活动的方式带来任何特殊价值。但是,您不太可能在域模型中找到除CORE之外的所有内容的良好归宿。SEGREGATED CORE采用直接的方法来结构化地标记CORE DOMAIN ....
Factoring out GENERIC SUBDOMAINS reduces clutter, and COHESIVE MECHANISMS serve to encapsulate complex operations. This leaves behind a more focused model, with fewer distractions that add no particular value to the way users conduct their activities. But you are unlikely ever to find good homes for everything in the domain model that is not CORE. The SEGREGATED CORE takes a direct approach to structurally marking off the CORE DOMAIN....
模型中的元素可能部分服务于核心领域,部分起辅助作用。核心元素可能与通用元素紧密耦合。核心的概念凝聚力可能不强或不明显。所有这些混乱和纠缠阻碍了核心。设计师无法清楚地看到最重要的关系,导致设计薄弱。
Elements in the model may partially serve the CORE DOMAIN and partially play supporting roles. CORE elements may be tightly coupled to generic ones. The conceptual cohesion of the CORE may not be strong or visible. All this clutter and entanglement chokes the CORE. Designers can’t clearly see the most important relationships, leading to a weak design.
通过分解通用子域,您可以清除域中的一些模糊细节,使核心更加清晰。但识别和澄清所有这些子域是一项艰巨的工作,其中一些似乎不值得费心。同时,最重要的核心域与残留物纠缠在一起。
By factoring out GENERIC SUBDOMAINS, you clear away some of the obscuring detail from the domain, making the CORE more visible. But it is hard work identifying and clarifying all these subdomains, and some of them don’t seem worth the trouble. Meanwhile, the all-important CORE DOMAIN is left entangled with the residue.
所以:
Therefore:
重构模型以将核心概念与支持参与者(包括定义不明确的参与者)分离,并加强核心的凝聚力,同时减少其与其他代码的耦合。将所有通用或支持元素分解到其他对象中,并将它们放入其他包中,即使这意味着以分离高度耦合的元素的方式重构模型。
Refactor the model to separate the CORE concepts from supporting players (including ill-defined ones) and strengthen the cohesion of the CORE while reducing its coupling to other code. Factor all generic or supporting elements into other objects and place them into other packages, even if this means refactoring the model in ways that separate highly coupled elements.
这基本上采用了我们应用于GENERIC SUBDOMAINS的相同原则,但方向不同。可以识别对我们的应用程序至关重要的内聚子域,并将其划分为它们自己的连贯包。如何处理留下的未分化质量很重要,但并不那么重要。它可以或多或少地留在原处,或根据突出的类别放入包中。最终,越来越多的残留物可以被分解为GENERIC SUBDOMAINS ,但在短期内,任何简单的解决方案都可以,只要保留对SEGREGATED CORE的关注即可。
This is basically taking the same principles we applied to GENERIC SUBDOMAINS but from the other direction. The cohesive subdomains that are central to our application can be identified and partitioned into coherent packages of their own. What is done with the undifferentiated mass left behind is important, but not as important. It can be left more or less where it was, or placed into packages based on prominent classes. Eventually, more and more of the residue can be factored into GENERIC SUBDOMAINS, but in the short term any easy solution will do, just so the focus on the SEGREGATED CORE is retained.
重构为SEGREGATED CORE所需的步骤通常如下:
The steps needed to refactor to SEGREGATED CORE are typically something like these:
1.确定一个核心子域(可能来自提炼文档)。
1. Identify a CORE subdomain (possibly drawing from the distillation document).
2. Move related classes to a new MODULE, named for the concept that relates them.
3.重构代码以切断不直接表达概念的数据和功能。将删除的方面放入其他包中的(可能是新的)类中。尝试将它们与概念相关的任务放在一起,但不要浪费太多时间追求完美。专注于清理CORE子域,并使从它到其他包的引用明确且不言自明。
3. Refactor code to sever data and functionality that are not directly expressions of the concept. Put the removed aspects into (possibly new) classes in other packages. Try to place them with conceptually related tasks, but don’t waste too much time being perfect. Keep focused on scrubbing the CORE subdomain and making the references from it to other packages explicit and self-explanatory.
4.重构新分离的核心模块,使其关系和交互更简单、更具沟通性,并最小化和澄清其与其他模块的关系。(这将成为持续的重构目标。)
4. Refactor the newly SEGREGATED CORE MODULE to make its relationships and interactions simpler and more communicative, and to minimize and clarify its relationships with other MODULES. (This becomes an ongoing refactoring objective.)
5.对另一个CORE子域重复此操作,直到SEGREGATED CORE完成。
5. Repeat with another CORE subdomain until the SEGREGATED CORE is complete.
隔离核心有时会使与紧密耦合的非核心类的关系变得更加模糊甚至更加复杂,但这种成本与澄清核心领域并使其更容易工作相比,是值得的。
Segregating the CORE will sometimes make relationships with tightly coupled non-CORE classes more obscure or even more complicated, but that cost is outweighed by the benefit of clarifying the CORE DOMAIN and making it much easier to work on.
隔离核心将帮助您增强核心域的凝聚力。分解模型有很多种有意义的方法,有时在创建隔离核心时,可能会破坏凝聚力良好的模块,牺牲凝聚力来增强核心域的凝聚力。这是净收益,因为企业软件的最大增值来自模型的企业特定方面。
The SEGREGATED CORE will let you enhance the cohesion of that CORE DOMAIN. There are many meaningful ways of breaking down a model, and sometimes in the creation of a SEGREGATED CORE a nicely cohesive MODULE may be broken, sacrificing that cohesion for the sake of bringing out the cohesiveness of the CORE DOMAIN. This is a net gain, because the greatest value-added of enterprise software comes from the enterprise-specific aspects of the model.
当然,另一个代价是,分离核心需要做大量工作。必须承认,决定采用分离核心可能会让开发人员参与整个系统的变更。
The other cost, of course, is that segregating the CORE is a lot of work. It must be acknowledged that a decision to go to a SEGREGATED CORE will potentially absorb developers in changes all over the system.
当你拥有一个对于系统至关重要的大型有界上下文,但模型的重要部分却被大量支持功能所掩盖时,就该砍掉SEGREGATED CORE了。
The time to chop out a SEGREGATED CORE is when you have a large BOUNDED CONTEXT that is critical to the system, but where the essential part of the model is being obscured by a great deal of supporting capability.
与许多战略设计决策一样,整个团队必须一起转向隔离核心。此步骤需要团队决策流程以及纪律严明、协调一致的团队来执行决策。挑战在于约束每个人使用相同的核心定义,同时不冻结该决策。由于核心领域就像设计的其他每个方面一样不断发展,因此使用隔离核心的经验将带来新的见解,了解什么是必需的,什么是支持元素。这些见解应该反馈到核心领域和隔离核心模块的完善定义中。
As with many strategic design decisions, an entire team must move to a SEGREGATED CORE together. This step requires a team decision process and a team disciplined and coordinated enough to carry out the decision. The challenge is to constrain everyone to use the same definition of the CORE while not freezing that decision. Because the CORE DOMAIN evolves just like every other aspect of a design, experience working with a SEGREGATED CORE will lead to new insights into what is essential and what is a supporting element. Those insights should feed back into a refined definition of the CORE DOMAIN and of the SEGREGATED CORE MODULES.
这意味着必须持续与团队分享新的见解,但个人(或编程搭档)不能单方面根据这些见解采取行动。无论联合决策的过程是什么,无论是共识还是团队领导指令,它都必须足够灵活,以便反复进行方向修正。沟通必须足够有效,以使每个人都在一个 CORE 视图中保持一致。
This means that new insights must be shared with the team on an ongoing basis, but an individual (or programming pair) cannot act on those insights unilaterally. Whatever the process is for joint decisions, whether consensus or team leader directive, it must be agile enough to make repeated course corrections. Communication must be effective enough to keep everyone together in one view of the CORE.
我们以图 15.2所示的模型作为货物运输协调软件的基础。
We start with the model shown in Figure 15.2 as the basis of software for cargo shipping coordination.
图 15.2
Figure 15.2
请注意,与实际应用可能需要的相比,这被高度简化了。现实的模型对于示例来说太过繁琐。因此,尽管这个示例可能不够复杂,不足以让我们走向SEGREGATED CORE,但请发挥想象力,将这个模型视为过于复杂,难以轻松解释并作为一个整体处理。
Note that this is highly simplified compared to what would likely be needed for a real application. A realistic model would be too cumbersome for an example. Therefore, although this example may not be complicated enough to drive us to a SEGREGATED CORE, take a leap of imagination to treat this model as being too complex to interpret easily and deal with as a whole.
那么,运输模式的本质是什么?通常,一个好的起点是“底线”。这可能会让我们关注定价和发票。但我们真的需要看看领域愿景声明。以下是其中的摘录。
Now, what is the essence of the shipping model? Usually a good place to start looking is the “bottom line.” This might lead us to focus on pricing and invoices. But we really need to look at the DOMAIN VISION STATEMENT. Here is an excerpt from this one.
...提高运营的可见性并提供工具以更快、更可靠地满足客户要求……
. . . Increase visibility of operations and provide tools to fulfill customer requirements faster and more reliably...
此应用程序并非为销售部门设计的。它将由公司的一线操作员使用。所以让我们将所有与金钱有关的问题归为(公认的重要)辅助角色。有人已将其中一些项目放入单独的包(账单)中。我们可以保留它,并进一步认识到它起着辅助作用。
This application is not being designed for the sales department. It is going to be used by the front-line operators of the company. So let’s relegate all money-related issues to (admittedly important) supporting roles. Someone has already placed some of these items into a separate package (Billing). We can keep that, and further recognize that it plays a supporting role.
重点需要放在货物处理上:根据客户要求交付货物。提取与这些活动最直接相关的类会在名为Delivery的新包中生成一个SEGREGATED CORE ,如图15.3所示。
The focus needs to be on the cargo handling: delivery of the cargo according to customer requirements. Extracting the classes most directly involved in these activities produces a SEGREGATED CORE in a new package called Delivery, as shown in Figure 15.3.
图15.3. 按照客户要求进行可靠交付是该项目的核心目标。
Figure 15.3. Reliable delivery in adherence with customer requirements is the core goal of this project.
在大多数情况下,类刚刚移入新包,但模型本身发生了一些变化。
For the most part, classes have just moved into the new package, but there have been a few changes to the model itself.
首先,客户协议现在限制了处理步骤。这是团队分离核心时出现的典型见解。随着注意力集中在有效、正确的交付上,很明显,客户协议中的交付约束是根本性的,应该在模型中明确说明。
First, the Customer Agreement now constrains the Handling Step. This is typical of the insights that tend to arise as the team segregates the CORE. As attention is focused on effective, correct delivery, it becomes clear that the delivery constraints in the Customer Agreement are fundamental and should be explicit in the model.
另一个变化更加务实。在重构模型中,客户协议直接附加到货物,而不需要通过客户进行导航。 (预订货物时必须附加它,就像客户一样。)在实际交货时,客户与操作的相关性不如协议本身。在另一个模型中,必须根据客户在装运过程中所扮演的角色找到正确的客户,然后查询其客户协议。这种互动会阻碍您打算讲述的关于模型的每个故事。新的关联使最重要的场景尽可能简单直接。现在,将客户完全从CORE中拉出来变得很容易。
The other change is more pragmatic. In the refactored model, the Customer Agreement is attached directly to the Cargo, rather than requiring a navigation through the Customer. (It will have to be attached when the Cargo is booked, just as the Customer is.) At actual delivery time, the Customer is not as relevant to operations as the agreement itself. In the other model, the correct Customer had to be found, according to the role it played in the shipment, and then queried for its Customer Agreement. This interaction would clog up every story you set out to tell about the model. The new association makes the most important scenarios as simple and direct as possible. Now it becomes easy to pull the Customer out of the CORE altogether.
那么,如何将Customer拉出来呢?重点是满足Customer 的要求,因此起初Customer似乎属于CORE 。但是,由于可以直接使用Customer Agreement,因此交付期间的交互通常不需要涉及Customer类。而且Customer的基本模型非常通用。
And what about pulling Customer out, anyway? The focus is on fulfilling the Customer’s requirements, so at first Customer seems to belong in the CORE. Yet the interactions during delivery do not usually need to involve the Customer class now that the Customer Agreement is available directly. And the basic model of a Customer is pretty generic.
有充分的理由将Leg保留在CORE中。我倾向于在CORE中采用极简主义,并且Leg与Transport Schedule、Routing Service和Location具有更紧密的结合力,而这些都不需要放在CORE中。但如果我想讲述的有关此模型的许多故事都涉及Legs,我会将其移到Delivery包中,并忍受与其他类分离的尴尬。
A strong argument could be made for Leg to remain in the CORE. I tend to be minimalist in the CORE, and the Leg has tighter cohesion with Transport Schedule, Routing Service, and Location, none of which needed to be in the CORE. But if a lot of the stories I wanted to tell about this model involved Legs, I’d move it into the Delivery package and suffer the awkwardness of its separation from those other classes.
在这个例子中,所有的类定义都与以前相同,但提炼通常需要重构类本身以分离通用和特定领域的职责,然后可以将其隔离。
In this example, all the class definitions are the same as before, but often distillation requires refactoring the classes themselves to separate the generic and domain-specific responsibilities, which can then be segregated.
现在我们有了SEGREGATED CORE,重构就完成了。但我们剩下的Shipping包只是“我们取出 CORE 后剩下的一切”。我们可以继续进行其他重构,以获得更具沟通性的包装,如图15.4所示。
Now that we have a SEGREGATED CORE, the refactoring is complete. But the Shipping package we are left with is just “everything left over after we pulled out the CORE.” We can follow up with other refactorings to get more communicative packaging, as shown in Figure 15.4.
图 15.4.隔离核心完成后,非核心子域的有意义的模块随之出现。
Figure 15.4. Meaningful MODULES for non-CORE subdomains follow after the SEGREGATED CORE is complete.
可能需要多次重构才能达到这一点;不必一次性完成。在这里,我们最终得到了一个SEGREGATED CORE包、一个GENERIC SUBDOMAIN和两个支持角色的特定领域包。更深入的洞察最终可能会为Customer生成一个GENERIC SUBDOMAIN,或者最终可能更专门用于运输。
It might take several refactorings to get to this point; it doesn’t have to be done all at once. Here, we’ve ended up with one SEGREGATED CORE package, one GENERIC SUBDOMAIN, and two domain-specific packages in supporting roles. Deeper insight might eventually produce a GENERIC SUBDOMAIN for Customer, or it might end up more specialized for shipping.
识别有用、有意义的模块是一项建模活动(如第 5 章所述)。开发人员和领域专家在知识处理过程中进行战略提炼合作。
Recognizing useful, meaningful MODULES is a modeling activity (as discussed in Chapter 5). Developers and domain experts collaborate in strategic distillation as part of the knowledge crunching process.
即使是核心领域 模型通常包含太多细节,因此传达整体情况可能会很困难。
Even the CORE DOMAIN model usually has so much detail that communicating the big picture can be difficult.
我们处理大型模型时,通常会将其分解为足够小、便于理解的子域,并将它们放在单独的模块中。这种简化的打包方式通常可以使复杂的模型易于管理。但有时创建单独的模块可能会掩盖甚至复杂化子域之间的交互。
We usually deal with a large model by breaking it into narrower subdomains that are small enough to be grasped and placing them in separate MODULES. This reductive style of packaging often works to make a complicated model manageable. But sometimes creating separate MODULES can obscure or even complicate the interactions between the subdomains.
当单独的模块中的子域之间存在大量交互时,要么必须在模块之间创建许多引用,这会抵消分区的大部分价值,要么必须使交互变得间接,这会使模型变得模糊。
When there is a lot of interaction between subdomains in separate MODULES, either many references will have to be created between MODULES, which defeats much of the value of the partitioning, or the interaction will have to be made indirect, which makes the model obscure.
考虑水平切片而不是垂直切片。多态性使我们能够忽略抽象类型实例之间的大量细节差异。如果MODULE之间的大多数交互都可以在多态接口级别表达,那么将这些类型重构为特殊的核心 MODULE可能是有意义的。
Consider slicing horizontally rather than vertically. Polymorphism gives us the power to ignore a lot of the detailed variation among instances of an abstract type. If most of the interactions across MODULES can be expressed at the level of polymorphic interfaces, it may make sense to refactor these types into a special CORE MODULE.
我们在这里不寻求技术技巧。只有当多态接口与领域中的基本概念相对应时,这才是有价值的技术。在这种情况下,分离这些抽象会解耦模块,同时提炼出更小、更有凝聚力的核心域。
We are not looking for a technical trick here. This is a valuable technique only when the polymorphic interfaces correspond to fundamental concepts in the domain. In that case, separating these abstractions decouples the MODULES while distilling a smaller and more cohesive CORE DOMAIN.
确定模型中最基本的概念,并将它们分解为不同的类、抽象类或接口。设计此抽象模型,使其能够表达重要组件之间的大部分交互。将此抽象整体模型放在其自己的MODULE中,而专门的详细实现类则留在子域定义的自己的MODULE中。
Identify the most fundamental concepts in the model and factor them into distinct classes, abstract classes, or interfaces. Design this abstract model so that it expresses most of the interaction between significant components. Place this abstract overall model in its own MODULE, while the specialized, detailed implementation classes are left in their own MODULES defined by subdomain.
现在,大多数专门的类将引用抽象核心模块,但不会引用其他专门模块。抽象核心简明扼要地介绍了主要概念及其相互作用。
Most of the specialized classes will now reference the ABSTRACT CORE MODULE but not the other specialized MODULES. The ABSTRACT CORE gives a succinct view of the main concepts and their interactions.
分解抽象核心的过程并不是机械的。例如,如果将模块中经常引用的所有类自动移至单独的模块,则可能的结果将是一团乱麻。对抽象核心进行建模需要深入了解关键概念及其在系统主要交互中所起的作用。换句话说,它是重构以获得更深入见解的一个例子。而且它通常需要进行大量的重新设计。
The process of factoring out the ABSTRACT CORE is not mechanical. For example, if all the classes that were frequently referenced across MODULES were automatically moved into a separate MODULE, the likely result would be a meaningless mess. Modeling an ABSTRACT CORE requires a deep understanding of the key concepts and the roles they play in the major interactions of the system. In other words, it is an example of refactoring to deeper insight. And it usually requires considerable redesign.
抽象核心最终应该看起来与提炼文档非常相似(如果两者都用于同一个项目,并且随着洞察力的加深,提炼文档会随着应用程序而发展)。当然,抽象核心将以代码编写,因此更加严格和完整。
The ABSTRACT CORE should end up looking a lot like the distillation document (if both were used on the same project, and the distillation document had evolved with the application as insight deepened). Of course, the ABSTRACT CORE will be written in code, and therefore more rigorous and more complete.
提炼不仅仅在总体层面上将领域的各个部分与核心分离。它还意味着通过不断重构以获得更深入的洞察力,推动深度模型和灵活的设计,细化这些子域,尤其是核心域。目标是设计一种使模型显而易见的设计,一种简单地表达领域的模型。深度模型将领域最基本的方面提炼为简单元素,这些元素可以组合起来解决应用程序的重要问题。
Distillation does not operate only on the gross level of separating parts of the domain away from the CORE. It also means refining those subdomains, especially the CORE DOMAIN, through continuously refactoring toward deeper insight, driving toward a deep model and supple design. The goal is a design that makes the model obvious, a model that expresses the domain simply. A deep model distills the most essential aspects of a domain into simple elements that can be combined to solve the important problems of the application.
尽管深度模型的突破在任何地方都会带来价值,但在核心领域,它可以改变整个项目的轨迹。
Although a breakthrough to a deep model provides value anywhere it happens, it is in the CORE DOMAIN that it can change the trajectory of an entire project.
当你遇到一个大型系统,但其结构却很差时,你该从哪里入手呢?在 XP 社区中,答案往往是以下之一:
When you encounter a large system that is poorly factored, where do you start? In the XP community, the answer tends to be either one of these:
1.从任何地方开始,因为一切都必须重构。
1. Just start anywhere, because it all has to be refactored.
2.从痛点开始。我将重构我需要的东西,以便完成我的具体任务。
2. Start wherever it is hurting. I’ll refactor what I need to in order to get my specific task done.
我不赞同这两种做法。第一种做法不切实际,除非在少数完全由顶级程序员组成的项目中。第二种做法往往只治标不治本,回避最严重的问题。最终,代码变得越来越难以重构。
I don’t hold with either of these. The first is impractical except in a few projects staffed entirely with top programmers. The second tends to pick around the edges, treating symptoms and ignoring root causes, shying away from the worst tangles. Eventually the code becomes harder and harder to refactor.
那么,如果你无法做到这一切,也无法承受痛苦,你该怎么办?
So, if you can’t do it all, and you can’t be pain-driven, what do you do?
1.在痛苦驱动的重构中,您要查看根源是否涉及核心域或核心与支持元素的关系。如果是,您就咬紧牙关,先修复它。
1. In a pain-driven refactoring, you look to see if the root involves the CORE DOMAIN or the relationship of the CORE to a supporting element. If it does, you bite the bullet and fix that first.
2.当你有自由重构的奢侈时,你首先要关注的是更好地分解CORE DOMAIN ,改善CORE的隔离,以及净化支持子域以使其成为GENERIC。
2. When you have the luxury of refactoring freely, you focus first on better factoring of the CORE DOMAIN, on improving the segregation of the CORE, and on purifying supporting subdomains to be GENERIC.
这就是如何让你的重构资金发挥最大效益。
This is how to get the most bang for your refactoring buck.
数千人独立协作,制作了艾滋病被子。
Thousands of people worked independently to create the AIDS Quilt.
一家硅谷小型设计公司已签约为卫星通信系统开发模拟器。工作进展顺利。正在开发一种模型驱动设计,可以表达和模拟各种网络状况和故障。
A small Silicon Valley design firm had been contracted to create a simulator for a satellite communications system. Work was progressing well. A MODEL-DRIVEN DESIGN was developing that could express and simulate a wide range of network conditions and failures.
但该项目的首席开发人员感到不安。问题本身就很复杂。为了澄清模型中错综复杂的关系,他们将设计分解为易于管理的连贯模块。现在有很多模块。开发人员应该在哪个包中查找某个功能?新类应该放在哪里?这些小包的真正含义是什么?它们是如何组合在一起的?还有更多需要构建的。
But the lead developers on the project were uneasy. The problem was inherently complex. Driven by the need to clarify the intricate relationships in the model, they had decomposed the design into coherent MODULES of manageable size. Now there were a lot of MODULES. Which package should a developer look in to find a particular aspect of functionality? Where should a new class be placed? What did some of these little packages really mean? How did they all fit together? And there was still more to build.
开发人员之间沟通良好,仍能弄清楚每天该做什么,但项目负责人并不满足于绕过可理解性的边缘。他们希望找到某种方式来组织设计,以便在进入下一个复杂程度时能够理解和操作它。
The developers communicated well with one another and could still figure out what to do from day to day, but the project leaders were not content to skirt the edge of comprehensibility. They wanted some way of organizing the design so that it could be understood and manipulated as it moved to the next level of complexity.
他们集思广益。有很多可能性。提出了替代的包装方案。也许一些文档可以概述系统,或者建模工具中类图的一些新视图可以引导开发人员找到正确的MODULE。但项目负责人对这些花招并不满意。
They brainstormed. There were a lot of possibilities. Alternative packaging schemes were proposed. Maybe some document could give an overview of the system, or some new views of the class diagram in the modeling tool could guide a developer to the right MODULE. But the project leaders weren’t satisfied with these gimmicks.
他们可以用一个简单的故事来描述他们的模拟,即数据如何通过基础设施进行编排,其完整性和路由由多层电信技术保证。这个故事的每个细节都在模型中,但故事的总体轮廓却无法看到。
They could tell a simple story of their simulation, of the way data would be marshaled through an infrastructure, its integrity and routing assured by layers of telecommunications technology. Every detail of that story was in the model, yet the broad arc of the story could not be seen.
领域中缺少一些基本概念。但这次不是对象模型中缺少一两个类,而是整个模型缺少一个结构。
Some essential concept from the domain was missing. But this time it was not a class or two missing from the object model, it was a missing structure for the model as a whole.
开发人员花了一两周时间仔细思考这个问题,然后开始形成想法。他们会在设计上强加一个结构。整个模拟器将被视为一系列与通信系统各方面相关的层。底层将代表物理基础设施,即从一个节点向另一个节点传输比特的基本能力。然后是数据包路由层,它将特定数据流如何定向的问题集中在一起。其他层将确定问题的其他概念层次。这些层将概述系统的故事。
After the developers mulled over the problem for a week or two, the idea began to jell. They would impose a structure on the design. The entire simulator would be viewed as a series of layers related to aspects of the communications system. The bottom layer would represent the physical infrastructure, the basic ability to transmit bits from one node to another. Then there would be a packet-routing layer that brought together the concerns of how a particular data stream would be directed. Other layers would identify other conceptual levels of the problem. These layers would outline their story of the system.
他们着手重构代码以符合新结构。必须重新定义模块,以免跨越层。在某些情况下,重构对象职责,以便每个对象都明确属于一个层。相反,在整个过程中,概念层本身的定义根据实际应用经验进行了改进。层、模块、和物体共同进化,直到最后整个设计都遵循了这种分层结构的轮廓。
They set out to refactor the code to conform to the new structure. MODULES had to be redefined so as not to span layers. In some cases, object responsibilities were refactored so that each object would clearly belong to one layer. Conversely, throughout this process the definitions of the conceptual layers themselves were refined based on the hands-on experience of applying them. The layers, MODULES, and objects coevolved until, in the end, the entire design followed the contours of this layered structure.
这些层不是模块或代码中的任何其他构件。它们是一套总体规则,用于约束整个设计过程中任何特定模块或对象的边界和关系,甚至在与其他系统的接口处也是如此。
These layers were not MODULES or any other artifact in the code. They were an overarching set of rules that constrained the boundaries and relationships of any particular MODULE or object throughout the design, even at interfaces with other systems.
实施这一顺序后,设计又恢复了舒适易懂的程度。人们大致知道在哪里可以找到某个特定功能。独立工作的个人可以做出大体一致的设计决策。复杂性的上限已被消除。
Imposing this order brought the design back to comfortable intelligibility. People knew roughly where to look for a particular function. Individuals working independently could make design decisions that were broadly consistent with each other. The complexity ceiling had been lifted.
即使采用模块化分解,大型模型也可能过于复杂而难以掌握。模块化将设计分成易于管理的部分,但可能有很多部分。此外,模块化并不一定能给设计带来一致性。从一个对象到另一个对象,从一个包到另一个包,可能会应用一堆设计决策,每个决策都有道理,但又各有特色。
Even with a MODULAR breakdown, a large model can be too complicated to grasp. The MODULES chunk the design into manageable bites, but there may be many of them. Also, modularity does not necessarily bring uniformity to the design. Object to object, package to package, a jumble of design decisions may be applied, each defensible but idiosyncratic.
有界上下文所施加的严格隔离可以防止损坏和混乱,但它本身并不能使我们更容易地看到整个系统。
The strict segregation imposed by BOUNDED CONTEXTS prevents corruption and confusion, but it does not, in itself, make it easier to see the system as a whole.
提炼确实有助于将注意力集中在核心领域,并让其他子领域扮演支持角色。但仍然需要了解支持元素及其与核心领域之间的关系,以及它们之间的关系。虽然核心领域理想情况下应该非常清晰易懂,不需要额外的指导,但我们并不总是处于这一点。
Distillation does help by focusing attention on the CORE DOMAIN and casting other subdomains in their supporting roles. But it is still necessary to understand the supporting elements and their relationships to the CORE DOMAIN—and to each other. And while the CORE DOMAIN would ideally be so clear and easily understood that no additional guidance would be needed, we are not always at that point.
在任何规模的项目中,人们都必须独立地处理系统的不同部分。如果没有任何协调或规则,就会出现对同一问题的不同风格和不同解决方案的混淆,从而很难理解各个部分如何组合在一起,也无法看到全局。了解设计的一部分不会转移到其他部分,因此项目最终会由不同模块的专家组成,他们无法在狭窄的范围之外互相帮助。持续集成会崩溃,有界上下文会支离破碎。
On a project of any size, people must work somewhat independently on different parts of the system. Without any coordination or rules, a confusion of different styles and distinct solutions to the same problems arises, making it hard to understand how the parts fit together and impossible to see the big picture. Learning about one part of the design will not transfer to other parts, so the project will end up with specialists in different MODULES who cannot help each other outside their narrow range. CONTINUOUS INTEGRATION breaks down and the BOUNDED CONTEXT fragments.
在一个大型系统中,如果没有任何总体原则允许根据元素在涵盖整个设计的模式中的作用来解释它们,开发人员将无法只见树木不见森林。我们需要能够理解单个部分在整体中的作用,而无需深入研究整体的细节。
In a large system without any overarching principle that allows elements to be interpreted in terms of their role in patterns that span the whole design, developers cannot see the forest for the trees. We need to be able to understand the role of an individual part in the whole without delving into the details of the whole.
“大规模结构”是一种语言,它可以让您大致讨论和理解系统。一组高级概念或规则(或两者兼而有之)为整个系统建立了设计模式。这种组织原则可以指导设计,也可以帮助理解。它有助于协调独立工作,因为存在一个共同的宏观概念:各个部分的角色如何塑造整体。
A “large-scale structure” is a language that lets you discuss and understand the system in broad strokes. A set of high-level concepts or rules, or both, establishes a pattern of design for an entire system. This organizing principle can guide design as well as aid understanding. It helps coordinate independent work because there is a shared concept of the big picture: how the roles of various parts shape the whole.
设计一个涵盖整个系统的规则或角色和关系模式,这样即使不详细了解各部分的职责,也能让人了解每个部分在整体中的位置。
Devise a pattern of rules or roles and relationships that will span the entire system and that allows some understanding of each part’s place in the whole—even without detailed knowledge of the part’s responsibility.
结构可能局限于一个有界上下文,但通常会跨越多个有界上下文,提供概念组织以将项目涉及的所有团队和子系统聚集在一起。良好的结构可以深入了解模型并补充提炼。
Structure may be confined to one BOUNDED CONTEXT but will usually span more than one, providing the conceptual organization to hold together all the teams and subsystems involved in the project. A good structure gives insight into the model and complements distillation.
您无法用 UML 表示大多数大型结构,也不需要这样做。大多数大型结构塑造并解释了模型和设计,但并未出现在其中。它们提供了有关设计的额外沟通层次。在本章的示例中,您将看到许多非正式的 UML 图,我已将有关大型结构的信息叠加在这些图上。
You can’t represent most large-scale structures in UML, and you don’t need to. Most large-scale structures shape and explain the model and design but do not appear in it. They provide an extra level of communication about the design. In the examples of this chapter, you’ll see many informal UML diagrams on which I’ve superimposed information about the large-scale structure.
当团队规模相对较小且模型不太复杂时,分解为名称明确的模块、一定程度的提炼以及开发人员之间的非正式协调就足以保持模型的井然有序。
When a team is reasonably small and the model is not too complicated, decomposition into well-named MODULES, a certain amount of distillation, and informal coordination among developers can be sufficient to keep the model organized.
大型结构可以挽救项目,但不合适的结构会严重阻碍开发。本章探讨了在此级别成功构建设计的模式。
Large-scale structure can save a project, but an ill-fitting structure can severely hinder development. This chapter explores patterns for successfully structuring a design at this level.
图 16.1. 一些大尺度结构的模式
Figure 16.1. Some patterns of large-scale structure
许多开发人员都经历过非结构化设计的代价。为了避免混乱,项目强加了以各种方式限制开发的架构。一些技术架构确实解决了技术问题,例如网络或数据持久性,但当架构开始涉足应用程序和领域模型领域时,它们可能会产生自己的问题。它们通常会阻止开发人员创建能够很好地解决具体问题的设计和模型。最雄心勃勃的架构甚至会剥夺应用程序开发人员对编程语言本身的熟悉程度和技术能力。无论是面向技术还是面向领域,随着需求的变化和理解的加深,冻结大量前期设计决策的架构可能会成为束缚。
Many developers have experienced the cost of an unstructured design. To avoid anarchy, projects impose architectures that constrain development in various ways. Some technical architectures do solve technical problems, such as networking or data persistence, but when architectures start venturing into the arena of the application and domain model, they can create problems of their own. They often prevent the developers from creating designs and models that work well for the specifics of the problem. The most ambitious ones can even take away from application developers the familiarity and technical power of the programming language itself. And whether technical or domain oriented, architectures that freeze a lot of up-front design decisions can become a straitjacket as requirements change and as understanding deepens.
虽然一些技术架构(如 J2EE)多年来已变得突出,但领域层的大规模结构尚未得到深入探索。不同应用程序的需求差异很大。
While some technical architectures (such as J2EE) have become prominent over the years, large-scale structure in the domain layer has not been explored much. Needs vary widely from one application to the next.
预先强加大规模结构可能会花费不菲。随着开发的进行,您几乎肯定会找到更合适的结构,甚至可能会发现规定的结构阻碍了您采取可以大大澄清或简化应用程序的设计路线。您可能能够使用部分结构,但您放弃了机会。当您尝试变通方法或尝试与架构师协商时,您的工作速度会变慢。但您的经理认为架构已经完成了。它应该使这个应用程序变得简单,所以为什么您不处理应用程序而不是处理所有这些架构问题呢?经理和架构团队甚至可能愿意接受意见,但如果每次更改都是一场英勇的战斗,那就太累了。
An up-front imposition of a large-scale structure is likely to be costly. As development proceeds, you will almost certainly find a more suitable structure, and you may even find that the prescribed structure is prohibiting you from taking a design route that would greatly clarify or simplify the application. You may be able to use some of the structure, but you’re forgoing opportunities. Your work slows down as you try workarounds or try to negotiate with the architects. But your managers think the architecture is done. It was supposed to make this application easy, so why aren’t you working on the application instead of dealing with all these architecture problems? The managers and architecture teams may even be open to input, but if each change is a heroic battle, it is too exhausting.
自由设计会产生一个没有人能理解的系统,而且很难维护。但是,架构可以用预先的设计假设束缚项目,并剥夺应用程序特定部分的开发人员/设计人员的太多权力。很快,开发人员就会简化应用程序以适应结构,或者他们会颠覆它,根本没有结构,从而再次带来不协调开发的问题。
Design free-for-alls produce systems no one can make sense of as a whole, and they are very difficult to maintain. But architectures can straitjacket a project with up-front design assumptions and take too much power away from the developers/designers of particular parts of the application. Soon, developers will dumb down the application to fit the structure, or they will subvert it and have no structure at all, bringing back the problems of uncoordinated development.
问题不在于是否存在指导规则,而在于这些规则的僵化和来源。如果设计规则真的符合实际情况,它们就不会成为阻碍,而是会将开发推向有益的方向,并提供一致性。
The problem is not the existence of guiding rules, but rather the rigidity and source of those rules. If the rules governing the design really fit the circumstances, they will not get in the way but actually push development in a helpful direction, as well as provide consistency.
所以:
Therefore:
让这个概念性的大型结构随着应用的发展而发展,在此过程中可能会转变为完全不同类型的结构。不要过度限制必须基于详细知识做出的详细设计和模型决策。
Let this conceptual large-scale structure evolve with the application, possibly changing to a completely different type of structure along the way. Don’t overconstrain the detailed design and model decisions that must be made with detailed knowledge.
各个部分都有自然或有用的组织和表达方式,但可能不适用于整体,因此强加全局规则会使这些部分变得不那么理想。选择使用大规模结构有利于整个模型的可管理性,而不是各个部分的最佳结构。因此,在统一结构和以最自然的方式表达各个组件的自由之间会有一些妥协。可以通过根据实际经验和领域知识选择结构并避免过度限制的结构来缓解这种情况。结构与领域和需求的真正契合实际上使详细的建模和设计变得更容易,因为它有助于快速消除许多选项。
Individual parts have natural or useful ways of being organized and expressed that may not apply to the whole, so imposing global rules makes these parts less ideal. Choosing to use a large-scale structure favors manageability of the model as a whole over optimal structuring of the individual parts. Therefore, there will be some compromise between unifying structure and freedom to express individual components in the most natural way. This can be mitigated by selecting the structure based on actual experience and knowledge of the domain and by avoiding over-constrictive structures. A really nice fit of structure to domain and requirements actually makes detailed modeling and design easier, by helping to quickly eliminate a lot of options.
这种结构还可以为设计决策提供捷径,原则上,这些决策可以通过在单个对象级别上进行工作来找到,但在实践中,这些决策会花费太长时间,并且结果不一致。当然,持续重构仍然是必要的,但这将使其成为一个更易于管理的过程,并有助于让不同的人提出一致的解决方案。
The structure can also give shortcuts to design decisions that could, in principle, be found by working on the individual object level, but would, in practice, take too long and have inconsistent results. Of course, continuous refactoring is still necessary, but this will make it a more manageable process and can help make different people come up with consistent solutions.
大规模结构通常需要适用于有界上下文。通过对实际项目的迭代,结构将失去将其与特定模型紧密绑定的特征,并发展出与领域概念轮廓相对应的特征。这并不意味着它对模型没有任何假设,但它不会将针对特定局部情况量身定制的想法强加于整个项目。它必须为不同上下文中的开发团队留下自由,让他们以符合其局部需求的方式改变模型。
A large-scale structure generally needs to be applicable across BOUNDED CONTEXTS. Through iteration on a real project, a structure will lose features that tightly bind it to a particular model and evolve features that correspond to CONCEPTUAL CONTOURS of the domain. This doesn’t mean that it will have no assumptions about the model, but it will not impose upon the entire project ideas tailored to a particular local situation. It has to leave freedom for development teams in distinct CONTEXTS to vary the model in ways that address their local needs.
此外,大型结构必须适应发展的实际限制。例如,设计师可能无法控制系统某些部分的模型,尤其是在外部或遗留子系统的情况下。可以通过改变结构以更好地适应特定的外部元素来处理。可以通过指定应用程序与外部元素的关系来处理。可以通过使结构足够宽松以适应尴尬的现实来处理。
Also, large-scale structures must accommodate practical constraints on development. For example, designers may have no control over the model of some parts of the system, especially in the case of external or legacy subsystems. This could be handled by changing the structure to better fit the specific external elements. It could be handled by specifying ways in which the application relates to externals. It might be handled by making the structure loose enough to flex around awkward realities.
与CONTEXT MAP不同,大规模结构是可选的。当成本和收益对大规模结构有利,并且找到合适的结构时,应该采用大规模结构。事实上,对于分解为MODULES即可理解的简单系统,大规模结构是不必要的。当可以找到一种结构来极大地阐明系统,而不会对模型开发施加不自然的约束时,应该应用大规模结构。因为不合适的结构比没有结构更糟糕,所以最好不要追求全面性,而是找到一个可以解决已出现问题的最小集合。少即是多。
Unlike the CONTEXT MAP, a large-scale structure is optional. One should be imposed when costs and benefits favor it, and when a fitting structure is found. In fact, it is not needed for systems that are simple enough to be understood when broken into MODULES. Large-scale structure should be applied when a structure can be found that greatly clarifies the system without forcing unnatural constraints on model development. Because an ill-fitting structure is worse than none, it is best not to shoot for comprehensiveness, but rather to find a minimal set that solves the problems that have emerged. Less is more.
大规模结构可能非常有用,但仍存在一些例外情况,但这些例外情况需要以某种方式标记出来,以便开发人员可以假定该结构得到遵循,除非另有说明。如果这些例外情况开始增多,则需要更改或放弃该结构。
A large-scale structure can be very helpful and still have a few exceptions, but those exceptions need to be flagged somehow, so that developers can assume the structure is being followed unless otherwise noted. And if those exceptions start to get numerous, the structure needs to be changed or discarded.
如上所述,创建一个既能为开发人员提供必要的自由度又能避免混乱的结构绝非易事。尽管在软件系统的技术架构方面已经做了很多工作,但关于领域层的结构却很少发表。一些方法削弱了面向对象范式,例如那些按应用程序任务或用例细分领域的方法。整个领域仍未开发。我观察到在各种项目中出现了一些大型结构的一般模式。我将在本章中讨论其中的四种。其中一种可能适合您的需求,或者会为您量身定制适合您项目的结构。
As mentioned, it is no mean feat to create a structure that gives the necessary freedom to developers while still averting chaos. Although a lot of work has been done on technical architecture for software systems, little has been published on the structuring of the domain layer. Some approaches weaken the object-oriented paradigm, such as those that break down the domain by application task or by use case. This whole area is still undeveloped. I’ve observed a few general patterns of large-scale structures that have emerged on various projects. I’ll discuss four in this chapter. One of these may fit your needs or lead to ideas for a structure tailored to your project.
隐喻思维在软件开发中非常普遍,尤其是在模型方面。但极限编程实践中的“隐喻”已经演变为一种使用隐喻来使整个系统开发井然有序的特定方式。
Metaphorical thinking is pervasive in software development, especially with models. But the Extreme Programming practice of “metaphor” has come to mean a particular way of using a metaphor to bring order to the development of a whole system.
就像防火墙可以保护建筑物免受火灾侵袭一样,软件“防火墙”可以保护本地网络免受外部大型网络的威胁。这个比喻影响了网络架构并塑造了整个产品类别。消费者可以使用多种相互竞争的防火墙——这些防火墙是独立开发的,被认为可以互换。网络新手很容易掌握这个概念。整个行业和客户之间的这种共识在很大程度上归功于这个比喻。
Just as a firewall can save a building from a fire raging through neighboring buildings, a software “firewall” protects the local network from the dangers of the larger networks outside. This metaphor has influenced network architectures and shaped a whole product category. Multiple competing firewalls—developed independently, understood to be somewhat interchangeable—are available for consumers. Novices to networking readily grasp the concept. This shared understanding throughout the industry and among customers is due in no small part to the metaphor.
然而,这是一个不准确的类比,其威力是双向的。使用防火墙的比喻导致了软件屏障的发展,这些屏障有时选择性不足,阻碍了理想的交换,同时又无法提供针对墙内威胁的保护。例如,无线局域网就很容易受到攻击。防火墙的清晰度是一个福音,但所有的比喻都有负担。1
Yet it is an inexact analogy, and its power cuts both ways. The use of the firewall metaphor has led to development of software barriers that are sometimes insufficiently selective and impede desirable exchanges, while offering no protection against threats originating within the wall. Wireless LANs, for example, are vulnerable. The clarity of the firewall has been a boon, but all metaphors carry baggage.1
软件设计往往非常抽象,难以理解。开发人员和用户都需要切实可行的方法来理解系统并共享整个系统的观点。
Software designs tend to be very abstract and hard to grasp. Developers and users alike need tangible ways to understand the system and share a view of the system as a whole.
从某种程度上来说,隐喻在我们的思维方式中根深蒂固,渗透到了每一个设计中。系统有“层”,“层”相互叠加。它们的“中心”有“内核”。但有时一个隐喻可以传达整个设计的中心主题,并为所有团队成员提供共同的理解。
On one level, metaphor runs so deeply in the way we think that it pervades every design. Systems have “layers” that “lay on top” of each other. They have “kernels” at their “centers.” But sometimes a metaphor comes along that can convey the central theme of a whole design and provide a shared understanding among all team members.
当这种情况发生时,系统实际上是由隐喻塑造的。开发人员将根据系统隐喻做出设计决策。这种一致性将使其他开发人员能够以相同的方式解释复杂系统的许多部分隐喻。开发人员和专家在讨论中有一个参考点,这个参考点可能比模型本身更具体。
When this happens, the system is actually shaped by the metaphor. A developer will make design decisions consistent with the system metaphor. This consistency will enable other developers to interpret the many parts of a complex system in terms of the same metaphor. The developers and experts have a reference point in discussions that may be more concrete than the model itself.
系统隐喻是一种松散、易于理解的大规模结构,与对象范式相协调。由于系统隐喻无论如何都只是领域的类比,因此不同的模型可以以近似的方式映射到它,这使得它可以应用于多个有界上下文,有助于协调它们之间的工作。
A SYSTEM METAPHOR is a loose, easily understood, large-scale structure that it is harmonious with the object paradigm. Because the SYSTEM METAPHOR is only an analogy to the domain anyway, different models can map to it in an approximate way, which allows it to be applied in multiple BOUNDED CONTEXTS, helping to coordinate work between them.
系统隐喻已成为一种流行的方法,因为它是极限编程的核心实践之一(Beck 2000)。不幸的是,很少有项目发现真正有用的隐喻,人们试图将这个想法推向适得其反的领域。一个有说服力的隐喻会带来风险,即设计将采用对手头问题不利的类比方面,或者类比虽然很诱人,但可能并不恰当。
SYSTEM METAPHOR has become a popular approach because it is one of the core practices of Extreme Programming (Beck 2000). Unfortunately, few projects have found really useful METAPHORS, and people have tried to push the idea into domains where it is counterproductive. A persuasive metaphor introduces the risk that the design will take on aspects of the analogy that are not desirable for the problem at hand, or that the analogy, while seductive, may not be apt.
话虽如此,系统隐喻是一种众所周知的大型结构形式,在某些项目中很有用,它很好地说明了结构的一般概念。
That said, SYSTEM METAPHOR is a well-known form of large-scale structure that is useful on some projects, and it nicely illustrates the general concept of a structure.
所以:
Therefore:
当系统出现一个具体的类比,能够吸引团队成员的想象力,并似乎能引导思维朝着有用的方向发展时,就将其作为一个大规模结构。围绕这个隐喻组织设计,并将其吸收到UBIQUITOUS LANGUAGE中。系统隐喻既应该促进有关系统的交流,又应该指导系统的开发。这可以提高系统不同部分的一致性,甚至可能跨越不同的BOUNDED CONTEXTS。但由于所有隐喻都是不精确的,因此要不断重新审视隐喻是否过度扩展或不恰当,并准备在它妨碍时将其放弃。
When a concrete analogy to the system emerges that captures the imagination of team members and seems to lead thinking in a useful direction, adopt it as a large-scale structure. Organize the design around this metaphor and absorb it into the UBIQUITOUS LANGUAGE. The SYSTEM METAPHOR should both facilitate communication about the system and guide development of it. This increases consistency in different parts of the system, potentially even across different BOUNDED CONTEXTS. But because all metaphors are inexact, continually reexamine the metaphor for overextension or inaptness, and be ready to drop it if it gets in the way.
因为大多数项目中并没有出现有用的隐喻,所以 XP 社区中的一些人开始谈论朴素隐喻,他们指的是领域模型本身。
Because a useful metaphor doesn’t present itself on most projects, some in the XP community have come to talk of the naive metaphor, by which they mean the domain model itself.
这个术语的一个问题是,成熟的领域模型绝不是幼稚的。事实上,“工资单处理就像一条装配线”可能比经过与领域专家多次反复的知识分析而得出的模型更为幼稚的观点,并且该模型已经通过紧密结合到工作应用程序的实现中而得到证明。
One trouble with this term is that a mature domain model is anything but naive. In fact, “payroll processing is like an assembly line” is likely a much more naive view than a model that is the product of many iterations of knowledge crunching with domain experts, and that has been proven by being tightly woven into the implementation of a working application.
“天真的隐喻”这个词语应该被淘汰。
The term naive metaphor should be retired.
系统隐喻并非对所有项目都有用。一般来说,大规模结构并不是必不可少的。在极限编程的 12 项实践中,系统隐喻的作用可以由通用语言来实现。当项目找到合适的语言时,应该用系统隐喻或其他大规模结构来增强该语言。
SYSTEM METAPHORS are not useful on all projects. Large-scale structure in general is not essential. In the 12 practices of Extreme Programming, the role of a SYSTEM METAPHOR could be fulfilled by a UBIQUITOUS LANGUAGE. Projects should augment that LANGUAGE with SYSTEM METAPHORS or other large-scale structures when they find one that fits well.
本书中,各个对象都被分配了一组狭窄的相关职责。职责驱动设计也适用于更大规模。
Throughout this book, individual objects have been assigned narrow sets of related responsibilities. Responsibility-driven design also applies to larger scales.
当每个单独的对象都有手工制定的职责时,就没有指导方针、没有统一性,也没有能力一起处理大量领域。为了使大型模型具有连贯性,对这些职责的分配施加一些结构是有用的。
When each individual object has handcrafted responsibilities, there are no guidelines, no uniformity, and no ability to handle large swaths of the domain together. To give coherence to a large model, it is useful to impose some structure on the assignment of those responsibilities.
当你对某个领域有了深刻的理解后,大范围的模式就开始显现出来。有些领域具有自然的分层。某些概念和活动是在其他元素的背景下发生的,这些元素会因不同的原因而独立地以不同的速度发生变化。我们如何利用这种自然结构,使其更加明显和有用?这种分层表明了分层,这是最成功的架构设计模式之一(Buschmann 等人,1996 年)。
When you gain a deep understanding of a domain, broad patterns start to become visible. Some domains have a natural stratification. Certain concepts and activities take place against a background of other elements that change independently and at a different rate for different reasons. How can we take advantage of this natural structure, make it more visible and useful? This stratification suggests layering, one of the most successful architectural design patterns (Buschmann et al. 1996, among others).
层是系统的分区,其中每个分区的成员知道并能够使用“下层”的服务,但不知道“上层”的服务,并且独立于“上层”。绘制模块的依赖关系时,通常会这样布局:带有依赖项的模块出现在其依赖项下方。这样,层有时会自行分类,使得较低层中的任何对象在概念上都不依赖于较高层中的对象。
Layers are partitions of a system in which the members of each partition are aware of and are able to use the services of the layers “below,” but unaware of and independent of the layers “above.” When the dependencies of MODULES are drawn, they are often laid out so that a MODULE with dependents appears below its dependents. In this way, layers sometimes sort themselves out so that none of the objects in the lower levels is conceptually dependent on those in higher layers.
但是这种临时分层虽然可以使依赖关系的追踪变得更容易(有时也有一些直观的意义),但并不能提供对模型的深入了解或指导建模决策。我们需要一些更有目的性的东西。
But this ad hoc layering, while it can make tracing dependencies easier—and sometimes makes some intuitive sense—doesn’t give much insight into the model or guide modeling decisions. We need something more intentional.
图 16.2. 临时分层:这些包是用于什么的?
Figure 16.2. Ad hoc layering: What are these packages about?
在具有自然分层的模型中,可以围绕主要职责定义概念层,将分层和职责驱动设计的两个强大原则结合起来。
In a model with a natural stratification, conceptual layers can be defined around major responsibilities, uniting the two powerful principles of layering and responsibility-driven design.
这些职责必须比通常分配给单个对象的职责要广泛得多,正如稍后将要举例说明的那样。在设计单个MODULES和AGGREGATE时,它们被分解以使其保持在其中一个主要职责的范围内。这种命名的职责分组本身可以增强模块化系统的可理解性,因为MODULES的职责可以更容易解释。但是将高级职责与分层相结合为我们提供了系统的组织原则。
These responsibilities must be considerably broader than those typically assigned to individual objects, as examples will illustrate shortly. As individual MODULES and AGGREGATES are designed, they are factored to keep them within the bounds of one of these major responsibilities. This named grouping of responsibilities by itself could enhance the comprehensibility of a modularized system, since the responsibilities of MODULES could be more readily interpreted. But combining high-level responsibilities with layering gives us an organizing principle for a system.
所以:
Therefore:
查看模型中的概念依赖关系以及领域不同部分的变化率和变化来源。如果您确定了领域中的自然层,请将它们视为广泛的抽象职责。这些职责应该讲述系统的高级目的和设计。重构模型,使每个领域对象、AGGREGATE和MODULE的职责恰好适合一个层的职责。
Look at the conceptual dependencies in your model and the varying rates and sources of change of different parts of your domain. If you identify natural strata in the domain, cast them as broad abstract responsibilities. These responsibilities should tell a story of the high-level purpose and design of your system. Refactor the model so that the responsibilities of each domain object, AGGREGATE, and MODULE fit neatly within the responsibility of one layer.
这是一个相当抽象的描述,但通过几个例子就会明白。卫星通信模拟器本章的故事以职责分层作为开端。我见过职责分层在制造控制和财务管理等各种领域中得到有效运用。
This is a pretty abstract description, but it will become clear with a few examples. The satellite communications simulator whose story opened this chapter layered its responsibility. I have seen RESPONSIBILITY LAYERS used to good effect in domains as various as manufacturing control and financial management.
下面的示例详细探讨了责任层,以便让您感受任何类型的大规模结构的发现,以及它指导和约束建模和设计的方式。
The following example explores RESPONSIBILITY LAYERS in detail to give a feel for the discovery of a large-scale structure of any sort, and the way it guides and constrains modeling and design.
让我们看一下将责任层应用于前几章示例中讨论的货物运输应用程序的含义。
Let’s look at the implications of applying RESPONSIBILITY LAYERS to the cargo shipping application discussed in the examples of previous chapters.
当我们重新回顾这个故事时,团队在创建模型驱动设计和提炼核心领域方面取得了长足的进步。但随着设计的完善,他们在协调所有部分如何组合在一起方面遇到了麻烦。他们正在寻找一种能够突出系统主题并让每个人都保持一致的大规模结构。
As we rejoin the story, the team has made considerable progress creating a MODEL-DRIVEN DESIGN and distilling a CORE DOMAIN. But as the design fleshes out, they are having trouble coordinating how all the parts fit together. They are looking for a large-scale structure that can bring out the main themes of their system and keep everyone on the same page.
以下是该模型的代表性部分。
Here is a look at a representative part of the model.
图 16.3. 用于安排货物路线的基本航运领域模型
Figure 16.3. A basic shipping domain model for routing cargoes
图 16.4. 使用模型在预订期间安排货物路线
Figure 16.4. Using the model to route a cargo during booking
团队成员在航运领域已经深耕数月,他们注意到航运概念中存在一些自然的层次。讨论运输时间表(轮船和火车的预定航程)而不提及运输工具上的货物是相当合理的。谈论跟踪货物而不提及运输工具则更为困难。概念上的依赖关系非常清晰。团队可以很容易地区分两个层次:“操作”和这些操作的基础,他们称之为“能力”。
The team members have been steeped in the domain of shipping for months, and they have noticed some natural stratification of its concepts. It is quite reasonable to discuss transport schedules (the scheduled voyages of ships and trains) without referring to the cargoes aboard those transports. It is harder to talk about tracking a cargo without referring to the transport carrying it. The conceptual dependencies are pretty clear. The team can readily distinguish two layers: “Operations” and the substrate of those operations, which they dub “Capability.”
公司过去、当前和计划中的活动都收集到 Operations 层中。最明显的 Operations 对象是Cargo,它是公司大部分日常活动的重点。Route Specification是Cargo 的一个组成部分,用于指示交付要求。Itinerary是运营交付计划。这两个对象都是Cargo 的AGGREGATE的一部分,它们的生命周期与活动交付的时间框架相关。
Activities of the company, past, current, and planned, are collected into the Operations layer. The most obvious Operations object is Cargo, which is the focus of most of the day-to-day activity of the company. The Route Specification is an integral part of Cargo, indicating delivery requirements. The Itinerary is the operational delivery plan. Both of these objects are part of the Cargo’s AGGREGATE, and their life cycles are tied to the time frame of an active delivery.
这一层反映了公司为开展业务而利用的资源。中转站就是一个典型的例子。船舶按计划运行,并具有一定的载货能力,但这些能力可能会或可能不会得到充分利用。
This layer reflects the resources the company draws upon in order to carry out operations. The Transit Leg is a classic example. The ships are scheduled to run and have a certain capacity to carry cargo, which may or may not be fully utilized.
确实,如果我们专注于运营一支船队,那么Transit Leg应该位于运营层。但该系统的用户并不担心这个问题。(如果公司参与这两项活动并希望协调这两项活动,开发团队可能必须考虑不同的分层方案,也许有两个不同的层,例如“运输运营”和“货物运营”。)
True, if we were focused on operating a shipping fleet, Transit Leg would be in the Operations layer. But the users of this system aren’t worried about that problem. (If the company were involved in both those activities and wanted the two coordinated, the development team might have to consider a different layering scheme, perhaps with two distinct layers, such as “Transport Operations” and “Cargo Operations.”)
一个更棘手的决定是将客户放在哪里。在某些企业中,客户往往是短暂的:在包裹递送期间,他们会很有趣,然后大部分都会被遗忘,直到下一次。这种特性使得客户只是针对个人消费者的包裹递送服务的运营关注点。但我们假设的运输公司倾向于与客户培养长期关系,并且大多数工作来自回头客。考虑到业务用户的这些意图,客户属于潜在层。如您所见,这不是一个技术决策。这是捕获和传达领域知识的尝试。
A trickier decision is where to place Customer. In some businesses, customers tend to be transient: they’re interesting while a package is being delivered and then mostly forgotten until next time. This quality would make customers only an operational concern for a parcel delivery service aimed at individual consumers. But our hypothetical shipping company tends to cultivate long-term relationships with customers, and most work comes from repeat business. Given these intentions of the business users, the Customer belongs in the potential layer. As you can see, this was not a technical decision. It was an attempt to capture and communicate knowledge of the domain.
由于Cargo和Customer之间的关联只能沿一个方向遍历,因此Cargo REPOSITORY需要一个查询来查找特定Customer的所有Cargo。无论如何,这样设计都是有充分理由的,但随着大规模结构的实施,这现在已成为一项要求。
Because the association between Cargo and Customer can be traversed in only one direction, the Cargo REPOSITORY will need a query that finds all Cargoes for a particular Customer. There were good reasons to design it that way anyway, but with the imposition of the large-scale structure, it is now a requirement.
图 16.5。查询替换了违反分层的双向关联。
Figure 16.5. A query replaces a bidirectional association that violates the layering.
图 16.6. 第一遍分层模型
Figure 16.6. A first-pass layered model
虽然运营和能力之间的区别使情况更加清晰,但秩序仍在不断发展。经过几周的试验,团队将注意力集中在另一个区别上。在大多数情况下,两个初始层都关注现状或计划。但路由器(以及本例中排除的许多其他元素)不是当前运营现实或计划的一部分。它有助于做出有关更改这些计划的决策。团队定义了一个负责“决策支持”的新层。
While the distinction between Operations and Capability clarifies the picture, order continues to evolve. After a few weeks of experimentation, the team zeroes in on another distinction. For the most part, both initial layers focus on situations or plans as they are. But the Router (and many other elements excluded from this example) isn’t part of current operational realities or plans. It helps make decisions about changing those plans. The team defines a new layer responsible for “Decision Support.”
该软件的这一层为用户提供了规划和决策工具,并且可以自动执行某些决策(例如,在运输时间表发生变化时自动重新安排货物路线)。
This layer of the software provides the user with tools for planning and decision making, and it could potentially automate some decisions (such as automatically rerouting Cargoes when a transport schedule changes).
路由器是一种服务,可帮助订舱代理选择最佳方式发送货物。这使路由器完全处于决策支持中。
The Router is a SERVICE that helps a booking agent choose the best way to send a Cargo. This places the Router squarely in Decision Support.
此模型中的引用与这三个层一致,但有一个不一致的元素除外:Transport Leg上的“is preferred”属性。此属性的存在是因为公司在可能的情况下更愿意使用自己的船只,或者与其签订了优惠合同的某些其他公司的船只。“is preferred”属性用于使路由器偏向这些受青睐的运输方式。此属性与“Capability”无关。它是一种指导决策的政策。要使用新的RESPONSIBILITY LAYERS,必须重构模型。
The references within this model are all consistent with the three layers except for one discordant element: the “is preferred” attribute on Transport Leg. This attribute exists because the company prefers to use its own ships when it can, or the ships of certain other companies with which it has favorable contracts. The “is preferred” attribute is used to bias the Router toward these favored transports. This attribute has nothing to do with “Capability.” It is a policy that directs decision making. To use the new RESPONSIBILITY LAYERS, the model will have to be refactored.
图 16.7. 重构模型以符合新的分层结构
Figure 16.7. Refactoring the model to conform to the new layering structure
这种分解使路线偏好策略更加明确,同时使运输支路更加专注于运输能力的基本概念。基于对领域的深刻理解的大规模结构通常会推动模型朝着明确其含义的方向发展。
This factoring makes the Route Bias Policy more explicit while making Transport Leg more focused on the fundamental concept of transportation capability. A large-scale structure based on a deep understanding of the domain will often push the model in directions that clarify its meaning.
这个新模型如今已顺利融入大型结构中。
This new model now smoothly fits into the large-scale structure.
图 16.8. 重构后的模型
Figure 16.8. The restructured and refactored model
熟悉所选层的开发人员可以更容易地辨别各部分的作用和依赖关系。随着复杂性的增加,大规模结构的价值也会增加。
A developer accustomed to the chosen layers can more readily discern the roles and dependencies of the parts. The value of the large-scale structure increases as the complexity grows.
请注意,虽然我用修改后的 UML 图来说明此示例,但该图只是传达分层的一种方式。UML 不包含此符号,因此这是为读者强加的附加信息。如果代码是您项目的最终设计文档,那么拥有一个按层浏览类或至少按层报告类的工具会很有帮助。
Note that although I’m illustrating this example with a modified UML diagram, the drawing is just a way of communicating the layering. UML doesn’t include this notation, so this is additional information imposed for the sake of the reader. If code is the ultimate design document for your project, it would be helpful to have a tool for browsing classes by layer or at least for reporting them by layer.
一旦采用了大规模结构,后续的建模和设计决策就必须考虑到这一点。为了说明这一点,假设我们必须向这个已经分层的设计添加一个新功能。领域专家刚刚告诉我们,某些类别的危险材料适用路由限制。某些材料可能不允许在某些运输工具或某些港口运输。我们必须让路由器遵守这些规定。
Once a large-scale structure has been adopted, subsequent modeling and design decisions must take it into account. To illustrate, suppose that we must add a new feature to this already layered design. The domain experts have just told us that routing restrictions apply for certain categories of hazardous materials. Certain materials may not be allowed on some transports or in some ports. We have to make the Router obey these regulations.
有很多可能的方法。在缺乏大规模结构的情况下,一个有吸引力的设计是将这些路由规则纳入到拥有路线规范和危险品 (HazMat) 代码的对象(即Cargo )的责任中。
There are many possible approaches. In the absence of a large-scale structure, one appealing design would be to give the responsibility of incorporating these routing rules to the object that owns the Route Specification and the Hazardous Material (HazMat) code—namely the Cargo.
图 16.9. 危险货物运输路线的可能设计
Figure 16.9. A possible design for routing hazardous cargo
图 16.10
Figure 16.10
问题是,这种设计不适合大规模结构。危险品路线策略服务不是问题;它完全符合决策支持层的职责。问题在于货物(操作对象)对危险品路线策略服务(决策支持对象)的依赖。只要项目致力于这些层,就不能允许这种模型。它会让那些希望遵循该结构的开发人员感到困惑。
The trouble is, this design doesn’t fit the large-scale structure. The HazMat Route Policy Service is not the problem; it fits neatly into the responsibility of the Decision Support layer. The problem is the dependency of Cargo (an Operational object) on HazMat Route Policy Service (a Decision Support object). As long as the project is committed to these layers, this model cannot be allowed. It would confuse developers who expected the structure to be followed.
设计方案总是有很多种,我们只能选择另一种方案——一种遵循大规模结构规则的方案。HazMat路线策略服务没问题,但我们需要转移使用策略的责任。让我们尝试让路由器负责在搜索路线之前收集适当的策略。这意味着更改路由器接口以包含策略可能依赖的对象。这是一个可能的设计。
There are always many design possibilities, and we’ll just have to choose another one—one that follows the rules of the large-scale structure. The HazMat Route Policy Service is all right, but we need to move the responsibility for using the policy. Let’s try giving the Router the responsibility for collecting appropriate policies before searching for a route. This means changing the Router interface to include objects that policies might depend on. Here is a possible design.
图 16.11. 符合分层的设计
Figure 16.11. A design consistent with layering
下一页的图 16.12显示了一种典型的交互。
A typical interaction is shown in Figure 16.12 on the next page.
图 16.12
Figure 16.12
现在,这个设计不一定比另一个更好。他们两者都有优点和缺点。但如果项目中的每个人都以一致的方式做出决策,那么整个设计就会更容易理解,这值得在细节设计选择上做出一些适度的权衡。
Now, this isn’t necessarily a better design than the other. They both have pros and cons. But if everyone on a project makes decisions in a consistent way, the design as a whole will be much more comprehensible, and that is worth some modest trade-offs on detailed design choices.
如果结构迫使人们做出许多尴尬的设计选择,那么为了符合演变秩序,应该对其进行评估,甚至修改,甚至丢弃。
If the structure is forcing many awkward design choices, then in keeping with EVOLVING ORDER, it should be evaluated and perhaps modified or even discarded.
找到好的RESPONSIBILITY LAYERS或任何大型结构都取决于对问题域的理解和试验。如果您允许EVOLVING ORDER,则初始起点并不重要,尽管错误的选择确实会增加工作量。结构很可能会演变成无法识别的东西。因此,在考虑结构转换时,应尽可能应用此处建议的准则,就像从头开始选择时一样。
Finding good RESPONSIBILITY LAYERS, or any large-scale structure, is a matter of understanding the problem domain and experimenting. If you allow EVOLVING ORDER, the initial starting point is not critical, although a poor choice does add work. The structure may well evolve into something unrecognizable. So the guidelines suggested here should be applied when considering transformations of the structure as much as when choosing from scratch.
当图层被切换、合并、分割和重新定义时,这里有一些有用的特征需要寻找和保留。
As layers get switched out, merged, split, and redefined, here are some useful characteristics to look for and preserve.
•讲故事。各层应传达领域的基本现实或优先事项。选择大规模结构与其说是技术决策,不如说是业务建模决策。各层应体现业务的优先事项。
• Storytelling. The layers should communicate the basic realities or priorities of the domain. Choosing a large-scale structure is less a technical decision than a business modeling decision. The layers should bring out the priorities of the business.
•概念依赖性。“上”层中的概念应该在“下”层的背景下有意义,而下层概念应该独立有意义。
• Conceptual dependency. The concepts in the “upper” layers should have meaning against the backdrop of the “lower” layers, while the lower-layer concepts should be meaningful standing alone.
•概念轮廓。如果不同层的对象应该具有不同的变化率或不同的变化源,则该层会适应它们之间的剪切。
• CONCEPTUAL CONTOURS. If the objects of different layers should have different rates of change or different sources of change, the layer accommodates the shearing between them.
定义每个新模型的层并不总是需要从头开始。某些层会出现在相关域的整个系列中。
It isn’t always necessary to start from scratch in defining layers for each new model. Certain layers show up in whole families of related domains.
例如,在基于利用大型固定资本资产(如工厂或货船)的业务中,物流软件通常可以组织成“潜在”层(示例中“能力”层的另一个名称)和“运营”层。
For example, in businesses based on exploiting large fixed capital assets, such as factories or cargo ships, logistical software can often be organized into a “Potential” layer (another name for the “Capability” layer in the example) and an “Operations” layer.
•潜力。可以做什么?别管我们计划做什么。我们可以做什么?组织的资源(包括人员)以及这些资源的组织方式是潜力层的核心。与供应商签订的合同也定义了潜力。几乎任何业务领域都可以识别出这一层,但它是运输和制造业等业务中的一个重要部分,这些业务拥有相对较大的固定资本投资,可以推动业务发展。潜力还包括临时资产,但主要由临时资产驱动的业务可能会选择强调这一点的层,如后所述。(本例中,这一层称为“能力”。)
• Potential. What can be done? Never mind what we are planning to do. What could we do? The resources of the organization, including its people, and the way those resources are organized are the core of the Potential layer. Contracts with vendors also define potentials. This layer could be recognized in almost any business domain, but it is a prominent part of the story in those businesses, such as transportation and manufacturing, that have relatively large fixed capital investments that enable the business. Potential includes transient assets as well, but a business driven primarily by transient assets might choose layers that emphasize this, as discussed later. (This layer was called “Capability” in the example.)
•操作。正在做什么?我们设法利用这些潜力做了什么?与潜力层一样,这一层应该反映情况的现实,而不是我们希望它反映的情况是。在这一层中,我们试图看到我们自己的努力和活动:我们在销售什么,而不是什么使我们能够销售。操作对象引用甚至由潜在对象组成是很典型的,但潜在对象不应该引用操作层。
• Operation. What is being done? What have we managed to make of those potentials? Like the Potential layer, this layer should reflect the reality of the situation, rather than what we want it to be. In this layer we are trying to see our own efforts and activities: What we are selling, rather than what enables us to sell. It is very typical of Operational objects to reference or even be composed of Potential objects, but a Potential object shouldn’t reference the Operations layer.
在此类领域的许多现有系统中,或许大多数系统中,这两个层都涵盖了所有内容(尽管可能存在一些完全不同且更具启发性的细分)。它们跟踪当前情况和现行运营计划,并发布相关报告或文件。但跟踪并不总是足够的。当项目寻求指导或协助用户,或实现决策自动化时,还有一组额外的职责可以组织到运营之上的另一层中。
In many, perhaps most, existing systems in domains of this kind, these two layers cover everything (although there could be some entirely different and more revealing breakdown). They track the current situation and active operational plans and issue reports or documents about it. But tracking is not always enough. When projects seek to guide or assist users, or to automate decision making, there is an additional set of responsibilities that can be organized into another layer, above Operations.
•决策支持。应采取什么行动或制定什么政策?此层用于分析和决策。它根据来自较低层(如潜力或运营)的信息进行分析。决策支持软件可能会使用历史信息积极寻找当前和未来运营的机会。
• Decision Support. What action should be taken or what policy should be set? This layer is for analysis and decision making. It bases its analysis on information from lower layers, such as Potential or Operations. Decision Support software may use historical information to actively seek opportunities for current and future operations.
决策支持系统在概念上依赖于其他层(如运营层或潜力层),因为决策不是凭空做出的。许多项目使用数据仓库技术实现决策支持。该层成为一个独特的有界上下文,与运营软件具有客户/供应商关系。在其他项目中,它被更深入地集成,如前面的扩展示例。层的固有优势之一是较低层可以在没有较高层的情况下存在。这可以促进在旧操作系统之上分阶段引入或更高级别的增强。
Decision Support systems have conceptual dependencies on other layers such as Operations or Potential because decisions aren’t made in a vacuum. A lot of projects implement Decision Support using data warehouse technology. The layer becomes a distinct BOUNDED CONTEXT, with a CUSTOMER/SUPPLIER relationship with the Operations software. In other projects, it is more deeply integrated, as in the preceding extended example. And one of the intrinsic advantages of layers is that the lower layers can exist without the higher ones. This can facilitate phased introductions or higher-level enhancements built on top of older operational systems.
另一种情况是执行复杂的业务规则或法律要求的软件,它可以构成责任层。
Another case is software that enforces elaborate business rules or legal requirements, which can constitute a RESPONSIBILITY LAYER.
•策略。规则和目标是什么?规则和目标大多是被动的,但会限制其他层的行为。设计这些交互可能很微妙。有时策略作为参数传递给较低级别的方法。有时应用策略模式。策略与决策支持层,提供手段来寻求Policy所设定的目标,并受到Policy所设定的规则的约束。
• Policy. What are the rules and goals? Rules and goals are mostly passive, but constrain the behavior in other layers. Designing these interactions can be subtle. Sometimes a Policy is passed in as an argument to a lower level method. Sometimes the STRATEGY pattern is applied. Policy works well in conjunction with a Decision Support layer, which provides the means to seek the goals set by Policy, constrained by the rules set by Policy.
策略层可以使用与其他层相同的语言编写,但有时使用规则引擎实现。这并不一定将它们放在单独的有界上下文中。事实上,通过在两者之间严格使用相同的模型,可以减轻协调这些不同实现技术的难度。当规则基于与其适用对象不同的模型编写时,复杂性会大大增加,或者对象会变得简单以保持可管理性。
Policy layers can be written in the same language as the other layers, but they are sometimes implemented using rules engines. This doesn’t necessarily place them in a separate BOUNDED CONTEXT. In fact, the difficulty of coordinating such different implementation technologies can be eased by fastidiously using the same model across both. When rules are written based on a different model than the objects they apply to, either the complexity goes way up or the objects get dumbed down to keep things manageable.
图 16.13. 工厂自动化系统中的概念依赖关系和剪切点
Figure 16.13. Conceptual dependencies and shearing points in a factory automation system
许多企业并不以厂房和设备为基础来衡量其能力。在金融服务或保险业中,潜力在很大程度上取决于当前的运营。保险公司通过承保新保单协议承担新风险的能力取决于其当前业务的多样化程度。潜力层可能会合并到运营中,并会形成不同的层次。
Many businesses do not base their capability on plant and equipment. In financial services or insurance, to name two, the potential is to a large extent determined by current operations. An insurance company’s ability to take on a new risk by underwriting a new policy agreement is based on the diversification of its current business. The Potential layer would probably merge into Operations, and a different layering would evolve.
在这些情况下,经常凸显的一个方面就是对客户做出的承诺。
One area that often comes to the fore in these situations is commitments made to customers.
•承诺。我们承诺了什么?这一层具有政策的性质,因为它陈述了指导未来运营的目标,但它也具有运营的性质,因为承诺作为正在进行的业务活动的一部分而出现和变化。
• Commitment. What have we promised? This layer has the nature of Policy, in that it states goals that direct future operations, but it has the nature of Operations in that commitments emerge and change as a part of ongoing business activity.
图 16.14. 投资银行系统中的概念依赖关系和剪切点
Figure 16.14. Conceptual dependencies and shearing points in an investment banking system
潜力层和承诺层并不相互排斥。如果一个领域两者都很突出,比如一家拥有大量定制运输服务的运输公司,那么它可能会同时使用这两个层。其他更特定于这些领域的层也可能很有用。改变一些东西。实验。但最好保持分层系统简单;超过四层或五层就会变得难以处理。层数太多在讲述故事时就不那么有效了,而大规模结构旨在解决的复杂问题将以新的形式再次出现。大规模结构必须经过严格的提炼。
The Potential and Commitment layers are not mutually exclusive. A domain in which both are prominent, say a transportation company with a lot of custom shipping services, might use both. Other layers more specific to those domains might be useful too. Change things. Experiment. But it is best to keep the layering system simple; going beyond four or possibly five becomes unwieldy. Having too many layers isn’t as effective at telling the story, and the problems of complexity the large-scale structure was meant to solve will come back in a new form. The large-scale structure must be ferociously distilled.
虽然这五个层适用于一系列企业系统,但它们并未涵盖所有领域的突出职责。在其他情况下,试图将设计强行变成这种形状会适得其反,但可能存在一组自然的职责层可以发挥作用。对于与我们讨论的领域完全无关的领域,这些层可能必须完全原创。最终,您必须使用您的直觉,从某个地方开始,然后让ORDER EVOLVE发挥作用。
Although these five layers are applicable to a range of enterprise systems, they do not capture the salient responsibilities of all domains. In other cases, it would be counterproductive to try to force the design into this shape, but there may be a natural set of RESPONSIBILITY LAYERS that do work. For a domain completely unrelated to those we’ve discussed, these layers might have to be completely original. Ultimately, you have to use your intuition, start somewhere, and let the ORDER EVOLVE.
[知识水平] 是一组对象,描述另一组对象应如何表现。[Martin Fowler,《责任》,www.martinfowler.com ]
[A KNOWLEDGE LEVEL is] a group of objects that describes how another group of objects should behave. [Martin Fowler, “Accountability,” www.martinfowler.com]
当我们需要让模型本身的某些部分在用户手中可塑但又受到更广泛的规则约束时,知识级别可以解决问题。它满足了具有可配置行为的软件的要求,其中实体之间的角色和关系必须在安装时甚至运行时更改。
KNOWLEDGE LEVEL untangles things when we need to let some part of the model itself be plastic in the user’s hands yet constrained by a broader set of rules. It addresses requirements for software with configurable behavior, in which the roles and relationships among ENTITIES must be changed at installation or even at runtime.
在《分析模式》(Fowler 1996,第 24-27 页)中,该模式源于对组织内责任建模的讨论,后来应用于会计中的过账规则。尽管该模式出现在几个章节中,但它没有自己的章节,因为它与书中的大多数模式不同。知识级别不是像其他分析模式那样对领域进行建模,而是构建一个模型。
In Analysis Patterns (Fowler 1996, pp. 24–27), the pattern emerges from a discussion of modeling accountability within organizations, and it is later applied to posting rules in accounting. Although the pattern appears in several chapters, it doesn’t have a chapter of its own because it is different from most patterns in the book. Rather than modeling a domain, as the other analysis patterns do, KNOWLEDGE LEVEL structures a model.
要具体地看待这个问题,请考虑“责任制”模型。组织由人和小组织组成,并定义他们扮演的角色以及他们之间的关系。管理这些角色和关系的规则因组织而异。在一家公司,“部门”可能由“主管”领导,他向“副总裁”汇报。在另一家公司,“模块”由“经理”领导,他向“高级经理”汇报。还有“矩阵”组织,其中每个人出于不同的目的向不同的经理汇报。
To see the problem concretely, consider models of “accountability.” Organizations are made up of people and smaller organizations, and define the roles they play and the relationships between them. The rules governing those roles and relationships vary greatly for different organizations. At one company, a “department” might be headed by a “Director” who reports to a “Vice President.” In another company, a “module” is headed by a “Manager” who reports to a “Senior Manager.” Then there are “matrix” organizations, in which each person reports to different managers for different purposes.
典型的应用程序会做出一些假设。当这些假设不合适时,用户将开始以与预期不同的方式使用数据输入字段。应用程序的任何行为都会失败,因为语义被用户改变了。用户会为该行为开发解决方法,或者会获得更高的应用程序的底层功能被关闭。他们将被迫学习他们在工作中所做的事情与软件工作方式之间的复杂映射。他们永远不会得到很好的服务。
A typical application would make some assumptions. When those didn’t fit, users would start to use data-entry fields in a different way than they were intended. Any behavior the application had would misfire, as the semantics were changed by the users. Users would develop workarounds for the behavior, or would get the higher level features of the application shut off. They would be forced to learn complicated mappings between what they did in their jobs and the way the software works. They would never be served well.
当系统需要更改或更换时,开发人员迟早会发现这些功能的含义并不像表面上看起来的那样。它们在不同的用户群体或不同情况下可能具有截然不同的含义。在不破坏这些重叠用法的情况下进行任何更改都是一项艰巨的任务。将数据迁移到更定制的系统需要理解并编码所有这些怪癖。
When the system had to be changed or replaced, developers would discover (sooner or later) that the meanings of the features were not what they seemed. They might mean very different things in different user communities or in different situations. Changing anything without breaking these overlaid usages would be daunting. Data migration to a more tailored system would require understanding and coding for all those quirks.
一家中型公司的人力资源部门有一个简单的程序,用于计算工资和养老金供款。
The HR department of a medium-sized company has a simple program for calculating payroll and pension contributions.
图 16.15。旧模型对新要求有过度约束
Figure 16.15. The old model, overconstrained for new requirements
图 16.16. 使用旧模型表示的一些员工
Figure 16.16. Some employees represented using the old model
但现在,管理层决定办公室管理人员应该加入“固定收益”退休计划。问题是办公室管理人员是按小时计酬的,这种模式不允许混合。这种模式必须改变。
But now, the management has decided that the office administrators should go into the “defined benefit” retirement plan. The trouble is that office administrators are paid hourly, and this model does not allow mixing. The model will have to change.
下一个模型提议非常简单:只需消除约束。
The next model proposal is quite simple: just remove the constraints.
图 16.17. 所提出的模型现在约束不足
Figure 16.17. The proposed model, now underconstrained
图 16.18.员工可能与错误的计划相关联。
Figure 16.18. Employees can be associated with the wrong plan.
这种模式允许每个员工与任一退休计划相关联,因此每个办公室管理员都可以更换。管理层拒绝了这种模式,因为它不反映公司政策。一些管理员可以更换,而另一些则不能。或者可以更换看门人。管理层想要一个强制执行以下政策的模型:
This model allows each employee to be associated with either kind of retirement plan, so each office administrator can be switched. This model is rejected by management because it does not reflect company policy. Some administrators could be switched and others not. Or the janitor could be switched. Management wants a model that enforces the policy:
办公室管理人员是计时工,有固定福利退休计划。
Office administrators are hourly employees with defined-benefit retirement plans.
此策略表明“职位”字段现在代表一个重要的领域概念。开发人员可以重构,将该概念明确为“员工类型”。
This policy suggests that the “job title” field now represents an important domain concept. Developers could refactor to make that concept explicit as an “Employee Type.”
图 16.19。Type对象允许满足要求。
Figure 16.19. The Type object allows requirements to be met.
图 16.20. 每种员工类型都分配有一个退休计划。
Figure 16.20. Each Employee Type is assigned a Retirement Plan.
这些要求可以用通用语言 (UBIQUITOUS LANGUAGE)来表述如下:
The requirements can be stated in the UBIQUITOUS LANGUAGE as follows:
员工类型被分配给退休计划或工资单。
An Employee Type is assigned to either Retirement Plan or either payroll.
员工受到员工类型的限制。
Employees are constrained by the Employee Type.
编辑员工类型对象的权限将仅限于“超级用户”,他们仅当公司政策发生变化时才会进行更改。人事部门的普通用户可以更改员工或将其指向不同的员工类型。
Access to edit the Employee Type object will be restricted to a “superuser,” who will make changes only when company policy changes. An ordinary user in the personnel department can change Employees or point them at a different Employee Type.
这个模型满足了要求。开发人员感觉到了一两个隐含的概念,但目前这只是一种挥之不去的感觉。他们没有任何切实可行的想法可以实施,所以他们就此罢手。
This model satisfies the requirements. The developers sense an implicit concept or two, but it is just a nagging feeling at the moment. They don’t have any solid ideas to pursue, so they call it a day.
静态模型可能会引发问题。但是,如果系统完全灵活,允许呈现任何可能的关系,那么问题可能同样严重。这样的系统使用起来不方便,而且不允许执行组织自己的规则。
A static model can cause problems. But problems can be just as bad with a fully flexible system that allows any possible relationship to be presented. Such a system would be inconvenient to use and wouldn’t allow the organization’s own rules to be enforced.
为每个组织完全定制软件是不切实际的,因为即使每个组织都能支付定制软件的费用,组织结构也可能会频繁变化。
Fully customizing software for each organization is not practical because, even if each organization could pay for custom software, the organizational structure will likely change frequently.
因此,此类软件必须提供选项,让用户能够配置它以反映组织的当前结构。问题是,向模型对象添加此类选项会使它们变得难以操作。你添加的灵活性越多,这一切就会变得越复杂。
So such software must provide options to allow the user to configure it to reflect the current structure of the organization. The trouble is that adding such options to the model objects makes them unwieldy. The more flexibility you add, the more complex it all becomes.
在一个应用程序中,如果实体之间的角色和关系在不同情况下会发生变化,那么复杂性就会激增。无论是完全通用的模型还是高度定制的模型都无法满足用户的需求。对象最终会引用其他类型以涵盖各种情况,或者在不同情况下以不同方式使用属性。具有相同数据和行为的类可能会成倍增加,以适应不同的组装规则。
In an application in which the roles and relationships between ENTITIES vary in different situations, complexity can explode. Neither fully general models nor highly customized ones serve the users’ needs. Objects end up with references to other types to cover a variety of cases, or with attributes that are used in different ways in different situations. Classes that have the same data and behavior may multiply just to accommodate different assembly rules.
我们的模型中还嵌套着另一个关于我们的模型的模型。知识水平将模型的自我定义方面分开,并使其约束明确化。
Nestled into our model is another model that is about our model. A KNOWLEDGE LEVEL separates that self-defining aspect of the model and makes its constraints explicit.
知识层是REFLECTION模式领域层的一个应用,该模式用于许多软件架构和技术基础设施,Buschmann 等人在 1996 年对其进行了详细描述。REFLECTION通过使软件具有“自我意识”,并使其结构和行为的某些方面易于适应和改变,来适应不断变化的需求。这是通过将软件划分为“基础层”来实现的,该层承担操作责任用于应用程序,以及“元级别”,代表对软件结构和行为的了解。
KNOWLEDGE LEVEL is an application to the domain layer of the REFLECTION pattern, used in many software architectures and technical infrastructures and described well in Buschmann et al. 1996. REFLECTION accommodates changing needs by making the software “self-aware,” and making selected aspects of its structure and behavior accessible for adaptation and change. This is done by splitting the software into a “base level,” which carries the operational responsibility for the application, and a “meta level,” which represents knowledge of the structure and behavior of the software.
值得注意的是,该模式不被称为知识“层”。尽管REFLECTION与分层非常相似,但它涉及两个方向上的相互依赖关系。
Significantly, the pattern is not called a knowledge “layer.” As much as it resembles layering, REFLECTION involves mutual dependencies running in both directions.
Java 有一些最小的内置反射机制,以协议的形式用于询问类的方法等。此类机制允许程序询问有关其自身设计的问题。CORBA 具有更广泛但类似的反射协议。一些持久性技术扩展了该自我描述的丰富性,以支持数据库表和对象之间的部分自动映射。还有其他技术示例。此模式也可以应用于域层。
Java has some minimal built-in REFLECTION in the form of protocols for interrogating a class for its methods and so forth. Such mechanisms allow a program to ask questions about its own design. CORBA has somewhat more extensive but similar REFLECTION protocols. Some persistence technologies extend the richness of that self-description to support partially automated mapping between database tables and objects. There are other technical examples. This pattern can also be applied within the domain layer.
比较知识水平和反思的术语
Comparing the terminology of KNOWLEDGE LEVEL and REFLECTION
需要明确的是,编程语言的反射工具不用于实现领域模型的知识水平。这些元对象描述了语言构造本身的结构和行为。相反,知识水平必须由普通对象构建。
Just to be clear, the reflection tools of the programming language are not for use in implementing the KNOWLEDGE LEVEL of a domain model. Those meta-objects describe the structure and behavior of the language constructs themselves. Instead, the KNOWLEDGE LEVEL must be built of ordinary objects.
知识级别提供了两个有用的区别。首先,它专注于应用领域,与熟悉的REFLECTION用法相反。其次,它并不追求完全的通用性。正如SPECIFICATION比一般谓词更有用一样,对一组对象及其关系的一组非常专业的约束可能比通用框架更有用。知识级别更简单,可以传达设计者的具体意图。
The KNOWLEDGE LEVEL provides two useful distinctions. First, it focuses on the application domain, in contrast to familiar uses of REFLECTION. Second, it does not strive for full generality. Just as a SPECIFICATION can be more useful than a general predicate, a very specialized set of constraints on a set of objects and their relationships can be more useful than a generalized framework. The KNOWLEDGE LEVEL is simpler and can communicate the specific intent of the designer.
创建一组独特的对象,用于描述和约束基本模型的结构和行为。将这些问题分为两个“级别”,一个非常具体,另一个反映用户或超级用户可以自定义的规则和知识。
Create a distinct set of objects that can be used to describe and constrain the structure and behavior of the basic model. Keep these concerns separate as two “levels,” one very concrete, the other reflecting rules and knowledge that a user or superuser is able to customize.
像所有强大的想法一样,反思和知识水平可能令人陶醉。这种模式应该谨慎使用。它可以通过将操作对象从万事通的需求中解放出来,从而消除复杂性,但它引入的间接性确实会增加一些晦涩难懂之处。如果知识水平变得复杂,系统的行为对于开发人员和用户来说都会变得难以理解。配置它的用户(或超级用户)最终将需要程序员的技能——而且是元级程序员。如果他们犯了错误,应用程序就会出现错误。
Like all powerful ideas, REFLECTION and KNOWLEDGE LEVELS can be intoxicating. This pattern should be used sparingly. It can unravel complexity by freeing operations objects from the need to be jacks-of-all-trades, but the indirection it introduces does add some of that obscurity back in. If the KNOWLEDGE LEVEL becomes complex, the system’s behavior becomes hard to understand for developers and users alike. The users (or superuser) who configure it will end up needing the skills of a programmer—and a meta-level programmer at that. If they make mistakes, the application will behave incorrectly.
此外,数据迁移的基本问题并没有完全消失。当知识层的结构发生变化时,必须处理现有的操作层对象。新旧对象可能共存,但无论如何,都需要仔细分析。
Also, the basic problems of data migration don’t completely disappear. When a structure in the KNOWLEDGE LEVEL is changed, existing operations-level objects have to be dealt with. It may be possible for old and new to coexist, but one way or another, careful analysis is needed.
所有这些问题都给知识水平的设计者带来了沉重的负担。设计必须足够强大,不仅要处理开发中出现的情况,还要处理用户将来可能配置软件的任何情况。明智地应用知识水平,在定制至关重要且否则会扭曲设计的点上,知识水平可以解决很难以其他方式处理的问题。
All of these issues put a major burden on the designer of a KNOWLEDGE LEVEL. The design has to be robust enough to handle not only the scenarios presented in development, but also any scenario for which a user could configure the software in the future. Applied judiciously, to the points where customization is crucial and would otherwise distort the design, KNOWLEDGE LEVELS can solve problems that are very hard to handle any other way.
我们的团队成员回来了,经过一夜的睡眠,他们精神焕发,其中一人已经开始接近一个尴尬的点。为什么某些对象是否受到保护,而其他对象是否可自由编辑?受限制对象的集群让他想起了知识级别模式,他决定尝试将其作为查看模型的一种方式。他发现现有模型已经可以这样查看。
Our team members are back, and, refreshed from a night’s sleep, one of them has started to close in on one of the awkward points. Why were certain objects being secured while others were freely edited? The cluster of restricted objects reminded him of the KNOWLEDGE LEVEL pattern, and he decided to try it as a way of viewing the model. He found that the existing model could already be viewed this way.
图 16.21. 识别现有模型中隐含的知识水平
Figure 16.21. Recognizing the KNOWLEDGE LEVEL implicit in the existing model
受限编辑处于知识级别,而日常编辑处于操作级别。非常合适。该线以上的所有对象都描述了类型或长期政策。员工类型有效地将行为强加于员工。
The restricted edits were in the KNOWLEDGE LEVEL, while the day-to-day edits were in the operational level. A nice fit. All the objects above the line described types or longstanding policies. The Employee Type effectively imposed behavior on the Employee.
这位开发人员正在与同事分享他的见解,这时另一位开发人员又有了另一个见解。清晰地看到按知识水平组织的模型让她发现了前一天困扰她的事情。两个不同的概念被结合在同一个对象中。她在前一天使用的语言中听到了这一点,但没有说出具体原因:
The developer was sharing his insight with his colleagues when one of the other developers had another insight. The clarity of seeing the model organized by KNOWLEDGE LEVEL had let her spot what had been bothering her the previous day. Two distinct concepts were being combined in the same object. She had heard it in the language used on the previous day but hadn’t put her finger on it:
员工类型被分配给退休计划或工资单。
An Employee Type is assigned to either Retirement Plan or either payroll.
但这实际上不是UBIQUITOUS LANGUAGE中的一个陈述。模型中没有“工资单”。他们用他们想要的语言而不是他们拥有的语言说话。工资单的概念隐含在模型中,与员工类型归为一类。在知识水平被分离出来之前,这一点并不明显,而该关键短语中的所有元素都出现在同一个级别中……除了一个。
But that was not really a statement in the UBIQUITOUS LANGUAGE. There was no “payroll” in the model. They had spoken in the language they wanted, rather than the one they had. The concept of payroll was implicit in the model, lumped together with Employee Type. It hadn’t been so obvious before the KNOWLEDGE LEVEL was separated out, and the very elements in that key phrase all appeared in the same level together . . . except one.
Based on this insight, she refactored again to a model that does support that statement.
用户控制关联对象规则的需求促使团队采用具有隐含知识水平的模型。
The need for user control of the rules for associating objects drove the team to a model that had an implicit KNOWLEDGE LEVEL.
图 16.22。工资单现在很明确,与员工类型不同。
Figure 16.22. Payroll is now explicit, distinct from Employee Type.
图 16.23.现在每个员工类型都有一个退休计划和一个工资单。
Figure 16.23. Each Employee Type now has a Retirement Plan and a Payroll.
知识水平通过特征访问限制和“事物-事物”类型的关系得到暗示。一旦到位,它所提供的清晰度有助于产生另一种洞察力,即通过分解工资单来解开两个重要的领域概念。
KNOWLEDGE LEVEL was hinted at by the characteristic access restrictions and a “thing-thing” type relationship. Once it was in place, the clarity it afforded helped produce another insight that disentangled two important domain concepts by factoring out Payroll.
与其他大型结构一样,知识水平并非绝对必要。没有它,对象仍可工作,并且仍然可以找到并使用将员工类型与工资单分开的洞察力。也许有一天,这种结构似乎无法发挥作用,可以放弃。但就目前而言,它似乎讲述了一个关于系统的有用故事,并帮助开发人员应对模型。
KNOWLEDGE LEVEL, like other large-scale structures, isn’t strictly necessary. The objects will still work without it, and the insight that separated Employee Type from Payroll could still have been found and used. There may come a time when this structure doesn’t seem to be pulling its weight and can be dropped. But for now, it seems to tell a useful story about the system and helps developers grapple with the model.
乍一看,知识层似乎是责任层的一个特例,尤其是“策略”层,但事实并非如此。一方面,依赖关系在层之间是双向的,但在层中,下层独立于上层。
At first glance, KNOWLEDGE LEVEL looks like a special case of RESPONSIBILITY LAYERS, especially the “policy” layer, but it is not. For one thing, dependencies run in both directions between the levels, but with LAYERS, lower layers are independent of upper layers.
事实上,知识层面可以与大多数其他大型结构共存,提供额外的组织维度。
In fact, KNOWLEDGE LEVEL can coexist with most other large-scale structures, providing an additional dimension of organization.
机会出现在非常成熟、深入和精炼的模型中。可插拔组件框架通常只有在同一个领域中已经实施了一些应用程序后才会发挥作用。
Opportunities arise in a very mature model that is deep and distilled. A PLUGGABLE COMPONENT FRAMEWORK usually only comes into play after a few applications have already been implemented in the same domain.
当各种应用程序必须互操作时,所有应用程序都基于相同的抽象但独立设计,多个有界上下文之间的转换会限制集成。对于不紧密合作的团队来说,共享内核是不可行的。重复和碎片化会增加开发和安装的成本,并且互操作性变得非常困难。
When a variety of applications have to interoperate, all based on the same abstractions but designed independently, translations between multiple BOUNDED CONTEXTS limit integration. A SHARED KERNEL is not feasible for teams that do not work closely together. Duplication and fragmentation raise costs of development and installation, and interoperability becomes very difficult.
一些成功的项目将其设计分解为组件,每个组件负责特定类别的功能。通常所有组件都插入一个中央集线器,该集线器支持它们所需的任何协议,并知道如何与它们提供的接口进行通信。连接组件的其他模式也是可能的。这些接口和连接它们的集线器的设计必须协调一致,而内部设计则可以更加独立。
Some successful projects break down their design into components, each with responsibility for certain categories of functions. Usually all the components plug into a central hub, which supports any protocols they need and knows how to talk to the interfaces they provide. Other patterns of connecting components are also possible. The design of these interfaces and the hub that connects them must be coordinated, while more independence is possible designing the interiors.
一些广泛使用的技术框架支持这种模式,但这是次要问题。只有当技术框架解决了一些基本技术问题(例如分布或在不同应用程序之间共享组件)时,它才是必要的。基本模式是职责的概念组织。它可以很容易地应用于单个 Java 程序中。
Several widely used technical frameworks support this pattern, but that is a secondary issue. A technical framework is needed only if it solves some essential technical problem such as distribution, or sharing a component among different applications. The basic pattern is a conceptual organization of responsibilities. It can easily be applied within a single Java program.
所以:
Therefore:
提炼出接口和交互的抽象核心,并创建一个框架,允许自由替换这些接口的不同实现。同样,允许任何应用程序使用这些组件,只要它严格通过抽象核心的接口运行。
Distill an ABSTRACT CORE of interfaces and interactions and create a framework that allows diverse implementations of those interfaces to be freely substituted. Likewise, allow any application to use those components, so long as it operates strictly through the interfaces of the ABSTRACT CORE.
高级抽象在整个系统范围内被识别和共享;专业化发生在MODULES中。应用程序的中心枢纽是SHARED KERNEL内的ABSTRACT CORE。但封装组件背后可以存在多个BOUNDED CONTEXTS接口,因此当许多组件来自许多不同的来源,或者当组件封装预先存在的软件以便于集成时,这种结构特别方便。
High-level abstractions are identified and shared across the breadth of the system; specialization occurs in MODULES. The central hub of the application is an ABSTRACT CORE within a SHARED KERNEL. But multiple BOUNDED CONTEXTS can lie behind the encapsulated component interfaces, so that this structure can be especially convenient when many components are coming from many different sources, or when components are encapsulating preexisting software for integration.
这并不是说组件必须有不同的模型。如果团队持续集成,则可以在单个上下文中开发多个组件,或者他们可以定义另一个由一组密切相关的组件共同拥有的共享内核。所有这些策略都可以在可插拔组件的大规模结构中轻松共存。在某些情况下,另一种选择是使用已发布的语言作为集线器的插件接口。
This is not to say that components must have divergent models. Multiple components can be developed within a single CONTEXT if the teams CONTINUOUSLY INTEGRATE, or they can define another SHARED KERNEL held in common by a closely related set of components. All these strategies can coexist easily within a large-scale structure of PLUGGABLE COMPONENTS. Another option, in some cases, is to use a PUBLISHED LANGUAGE for the plug-in interface of the hub.
可插拔组件框架(PLUGGABLE COMPONENT FRAMEWORK)有一些缺点。其一,这是一种很难应用的模式。它要求接口设计精确,并且需要足够深的模型来捕捉抽象核心 ( ABSTRACT CORE ) 中的必要行为。另一个主要缺点是应用程序的选项有限。如果应用程序需要采用与核心域 (CORE DOMAIN)截然不同的方法,那么结构就会成为阻碍。开发人员可以专门化模型,但如果不更改所有不同组件的协议,他们就无法更改抽象核心(ABSTRACT CORE) 。因此,对核心 (CORE ) 进行持续改进(重构以获得更深入的洞察)的过程或多或少陷入了停滞状态。
There are a few downsides to a PLUGGABLE COMPONENT FRAMEWORK. One is that this is a very difficult pattern to apply. It requires precision in the design of the interfaces and a deep enough model to capture the necessary behavior in the ABSTRACT CORE. Another major downside is that applications have limited options. If an application needs a very different approach to the CORE DOMAIN, the structure will get in the way. Developers can specialize the model, but they can’t change the ABSTRACT CORE without changing the protocol of all the diverse components. As a result, the process of continuous refinement of the CORE, refactoring toward deeper insight, is more or less frozen in its tracks.
Fayad 和 Johnson (2000)仔细研究了多个领域中对可插拔组件框架的雄心勃勃的尝试,包括对 SEMATECH CIM 的讨论。此类框架的成功故事喜忧参半。可能最大的障碍是设计有用框架所需的理解成熟度。可插拔组件框架不应该是第一个应用于项目的大型结构,也不应该是第二个。最成功的例子是在多个专门应用程序完全开发之后出现的。
Fayad and Johnson (2000) give a good look at ambitious attempts at PLUGGABLE COMPONENT FRAMEWORKS in several domains, including a discussion of SEMATECH CIM. The success of such frameworks is a mixed story. Probably the biggest obstacle is the maturity of understanding needed to design a useful framework. A PLUGGABLE COMPONENT FRAMEWORK should not be the first large-scale structure applied on a project, nor the second. The most successful examples have followed after the full development of multiple specialized applications.
在生产计算机芯片的工厂中,成组的硅片(称为批次)从一台机器移动到另一台机器,经过数百道工序,直到印刷和蚀刻微型电路完成。工厂需要能够跟踪每个批次,记录对其进行的精确加工,然后指示工厂工人或自动化设备将其带到下一台合适的机器并应用下一个合适的工艺。这样的软件称为制造执行系统(MES)。
In a factory producing computer chips, groups (called lots) of silicon wafers are moved from one machine to another through hundreds of steps of processing until the microscopic circuitry being printed and etched into them is complete. The factory needs software that can track each individual lot, recording the exact processing that has been done to it, and then direct either factory workers or automated equipment to take it to the next appropriate machine and apply the next appropriate process. Such software is called a manufacturing execution system (MES).
数十家供应商的数百台不同机器投入使用,每一步都有精心定制的配方。开发能够处理如此复杂组合的 MES 软件是一项艰巨的任务,而且成本高昂。为此,行业联盟 SEMATECH 开发了 CIM 框架。
Hundreds of different machines from dozens of vendors are used, with carefully tailored recipes at each step of the way. Developing MES software that could deal with such a complex mix was daunting and prohibitively expensive. In response, an industry consortium, SEMATECH, developed the CIM Framework.
CIM 框架庞大而复杂,涉及很多方面,但这里主要涉及两个方面。首先,该框架为半导体 MES 领域的基本概念(即以ABSTRACT CORE形式出现的CORE DOMAIN)定义了抽象接口。这些接口定义包括行为和语义。
The CIM Framework is big and complicated and has many aspects, but two are relevant here. First, the framework defines abstract interfaces for the basic concepts of the semiconductor MES domain—in other words, the CORE DOMAIN in the form of an ABSTRACT CORE. These interface definitions include both behavior and semantics.
图 16.24. CIM 接口的高度简化子集,带有示例实现
Figure 16.24. A highly simplified subset of the CIM interfaces, with sample implementations
如果供应商生产了一台新机器,他们必须开发一个专门的Process Machine接口实现。如果他们遵循该接口,他们的机器控制组件应该可以插入任何基于 CIM 框架的应用程序。
If a vendor produces a new machine, they have to develop a specialized implementation of the Process Machine interface. If they adhere to that interface, their machine-control component should plug into any application based on the CIM Framework.
定义这些接口后,SEMATECH 定义了它们在应用程序中交互的规则。任何基于 CIM 框架的应用程序都必须实现一个协议,该协议托管实现这些接口的某些子集的对象。如果实现了这个协议,并且应用程序严格遵守抽象接口,那么应用程序就可以依靠承诺的这些接口的服务,无论实现如何。这些接口和使用它们的协议的组合构成了严格限制的大规模结构。
Having defined these interfaces, SEMATECH defined the rules by which they could interact in an application. Any application based on the CIM Framework would have to implement a protocol that hosted objects implementing some subset of those interfaces. If this protocol were implemented, and the application strictly observed the abstract interfaces, then the application could count on the promised services of those interfaces, regardless of implementation. The combination of those interfaces and the protocol for using them constitutes a tightly restrictive large-scale structure.
图 16.25.用户在下一台机器上放置一批货物并将此次移动记录到计算机中。
Figure 16.25. The user places a lot in the next machine and logs the move into the computer.
该框架具有非常具体的基础设施要求。它与 CORBA 紧密耦合,以提供持久性、事务、事件和其他技术服务。但有趣的是它定义了一个可插拔组件框架,它允许人们独立开发软件并将其顺利地集成到庞大的系统中。没有人知道这样一个系统的所有细节,但每个人都了解一个概述。
The framework has very specific infrastructure requirements. It is tightly coupled to CORBA to provide persistence, transactions, events, and other technical services. But the interesting thing about it is the definition of a PLUGGABLE COMPONENT FRAMEWORK, which allows people to develop software independently and smoothly integrate them into immense systems. No one knows all the details of such a system, but everyone understands an overview.
数千人如何独立工作,制作出一条由4万多块布片组成的被子?
How can thousands of people work independently to create a quilt of more than 40,000 panels?
几条简单的规则为艾滋病纪念被子提供了一个大规模的结构,细节留给了个人贡献者。请注意规则如何关注整体使命(纪念人们该项目旨在帮助人们应对因艾滋病而死亡的被子数量减少、被子组件的集成度提高以及将被子分成更大部分进行处理(例如将其折叠)等问题。
A few simple rules provide a large-scale structure for the AIDS Memorial Quilt, leaving the details to individual contributors. Notice how the rules focus on the overall mission (memorializing people who have died of AIDS), the features of a component that make integration practical, and the ability to handle the quilt in larger sections (such as folding it).
Here’s How to Create a Panel for the Quilt
[摘自艾滋病纪念被子项目网站,www.aidsquilt.org ]
[From the AIDS Memorial Quilt Project Web site, www.aidsquilt.org]
设计面板
Design the panel
写上您要缅怀的人的姓名。您可以随意添加其他信息,例如出生和死亡日期以及家乡。……[请]将每个面板限制为一个人……
Include the name of the person you are remembering. Feel free to include additional information such as the dates of birth and death, and a hometown. . . . [P]lease limit each panel to one individual . . . .
选择你的材料
Choose your materials
请记住,被子会多次折叠和展开,因此耐用性至关重要。由于胶水会随着时间的推移而变质,因此最好将东西缝在面板上。中等重量、无弹性的面料(如棉帆布或府绸)效果最好。
Remember that the Quilt is folded and unfolded many times, so durability is crucial. Since glue deteriorates with time, it is best to sew things to the panel. A medium-weight, non-stretch fabric such as a cotton duck or poplin works best.
您的设计可以是垂直的或水平的,但成品的折边面板必须为 3 英尺 x 6 英尺(90 厘米 x 180 厘米)——不多不少!裁剪布料时,每边多留 2-3 英寸用于折边。如果您自己无法折边,我们可以帮您折边。面板不需要填充物,但建议使用衬垫。衬垫有助于在将面板铺在地面上时保持面板清洁。它还有助于保持布料的形状。
Your design can be vertical or horizontal, but the finished, hemmed panel must be 3 feet by 6 feet (90 cm × 180 cm)—no more and no less! When you cut the fabric, leave an extra 2–3 inches on each side for a hem. If you can’t hem it yourself, we’ll do it for you. Batting for the panels is not necessary, but backing is recommended. Backing helps to keep panels clean when they are laid out on the ground. It also helps retain the shape of the fabric.
创建面板
Create the panel
在构建面板时您可能需要使用以下一些技术:
In constructing your panel you might want to use some of the following techniques:
• 贴花:将布料、字母和小纪念品缝制在背景布料上。不要依赖胶水——它不会持久。
• Appliqué: Sew fabric, letters and small mementos onto the background fabric. Do not rely on glue—it won’t last.
• 油漆:刷上纺织涂料或不褪色染料,或使用不褪色的墨水笔。请不要使用“蓬松”涂料;它太粘了。
• Paint: Brush on textile paint or color-fast dye, or use an indelible ink pen. Please don’t use “puffy” paint; it’s too sticky.
• 模板:用铅笔将您的图案描绘到织物上,提起模板,然后使用刷子涂抹纺织涂料或不可磨灭的记号笔。
• Stencil: Trace your design onto the fabric with a pencil, lift the stencil, then use a brush to apply textile paint or indelible markers.
• 拼贴:确保添加到面板上的任何材料都不会撕裂织物(为此,请避免使用玻璃和亮片),并确保避免使用非常笨重的物体。
• Collage: Make sure that whatever materials you add to the panel won’t tear the fabric (avoid glass and sequins for this reason), and be sure to avoid very bulky objects.
• 照片:添加照片或信件的最佳方式是将它们复印到熨烫转印纸上,熨烫到 100% 纯棉织物上,然后将织物缝到面板上。您也可以将照片放在透明塑料乙烯基上,然后将其缝到面板上(偏离中心以避免折叠)。
• Photos: The best way to include photos or letters is to photocopy them onto iron-on transfers, iron them onto 100% cotton fabric and sew that fabric to the panel. You may also put the photo in clear plastic vinyl and sew it to the panel (off-center so it avoids the fold).
本章讨论的大规模结构模式范围从非常松散的系统隐喻到限制性的可插拔组件框架。当然,其他结构也是可能的,即使在一般结构模式中,也有很多关于如何制定规则限制的选择。
The large-scale structure patterns discussed in this chapter range from the very loose SYSTEM METAPHOR to the restrictive PLUGGABLE COMPONENT FRAMEWORK. Other structures are possible, of course, and even within a general structural pattern, there is a lot of choice about how restrictive to make the rules.
例如,责任层规定了一种模型概念及其依赖关系的分解,但您可以添加指定层之间通信模式的规则。
For example, RESPONSIBILITY LAYERS dictate a kind of factoring of model concepts and their dependencies, but you could add rules that would specify communication patterns between the layers.
考虑一个制造工厂,其中软件将每个零件引导到一台机器,并根据某种配方进行加工。正确的流程由策略层命令并在操作层执行。但工厂车间不可避免地会犯错误。实际情况与软件规则不一致。现在,操作层必须反映世界的本来面目,这意味着当某个零件偶尔被放错机器时,必须无条件接受该信息。某种程度上,这种异常情况需要传达给更高层。决策层可以使用其他策略来纠正这种情况,也许是将零件重新路由到维修流程或者将其报废。但操作层对更高层一无所知。通信必须以不会在较低层和较高层之间产生双向依赖的方式进行。
Consider a manufacturing plant where software directs each part to a machine where it is processed according to some recipe. The correct process is ordered from a Policy layer and executed in an Operations layer. But inevitably there will be mistakes made on the factory floor. The actual situation will not be consistent with the rules of the software. Now, an Operations layer must reflect the world as it is, which means that when a part is occasionally put in the wrong machine, that information must be accepted unconditionally. Somehow, this exceptional condition needs to be communicated to a higher layer. A decision-making layer can then use other policies to correct the situation, perhaps by rerouting the part to a repair process or by scrapping it. But Operations does not know anything about higher layers. The communication has to be done in a way that doesn’t create two-way dependencies from the lower layers to the higher ones.
通常,这种信号传递是通过某种事件机制进行的。操作对象会在状态发生变化时生成事件。策略层对象会监听来自较低层的相关事件。当发生违反规则的事件时,规则会执行一个动作(规则定义的一部分)以做出适当的响应,或者可能会生成事件以造福更高层。
Typically, this signaling would be done through some kind of event mechanism. The Operations objects would generate events whenever their state changed. Policy layer objects would listen for events of interest from the lower layers. When an event occurred that violated a rule, the rule would execute an action (part of the rule’s definition) that makes the appropriate response, or it might generate an event for the benefit of some still higher layer.
在银行业示例中,资产价值发生变化(操作),从而改变了投资组合各部分的价值。当这些价值超出投资组合分配限额(政策)时,交易员可能会收到警报,交易员可以买入或卖出资产来纠正失衡。
In the banking example, the values of assets change (Operations), shifting the values of segments of a portfolio. When these values exceed portfolio allocation limits (Policy), perhaps a trader is alerted, who can buy or sell assets to redress the balance.
我们可以根据具体情况来解决这个问题,或者我们可以决定一个一致的模式,让每个人在特定层的对象交互中遵循这种模式。更严格的结构可以提高一致性,使设计更容易解释。如果结构合适,规则可能会促使开发人员进行更好的设计。不同的部分可能会更好地结合在一起。
We could figure this out on a case-by-case basis, or we could decide on a consistent pattern for everyone to follow in interactions of objects of particular layers. A more restrictive structure increases uniformity, making the design easier to interpret. If the structure fits, the rules are likely to push developers toward good designs. Disparate pieces are likely to fit together better.
另一方面,这些限制可能会剥夺开发人员所需的灵活性。非常特殊的通信路径可能不适用于跨BOUNDED CONTEXTS,尤其是在异构系统中使用不同的实现技术时。
On the other hand, the restrictions may take away flexibility that developers need. Very particular communication paths might be impractical to apply across BOUNDED CONTEXTS, especially in different implementation technologies, in a heterogeneous system.
因此,您必须抵制构建框架和规范大规模结构实施的诱惑。大规模结构最重要的贡献是概念连贯性,以及提供对领域的洞察。每条结构规则都应该使开发更容易。
So you have to fight the temptation to build frameworks and regiment the implementation of the large-scale structure. The most important contribution of the large-scale structure is conceptual coherence, and giving insight into the domain. Each structural rule should make development easier.
在这个行业正在摆脱过度前期设计的时代,一些人会将大规模结构视为瀑布式架构的倒退。但事实上,找到有用结构的唯一方法是对领域和问题有非常深入的理解,而获得这种理解的实际方法是迭代开发过程。
In an era when the industry is shaking off excessive up-front design, some will see large-scale structure as a throwback to the bad old days of waterfall architecture. But in fact, the only way a useful structure can be found is from a very deep understanding of the domain and the problem, and the practical way to that understanding is an iterative development process.
致力于发展秩序的团队必须勇敢地重新思考整个项目生命周期的大规模结构。团队不应该让自己陷入早期构想的结构中,因为当时没有人能够很好地理解领域或需求。
A team committed to EVOLVING ORDER must fearlessly rethink the large-scale structure throughout the project life cycle. The team should not saddle itself with a structure conceived of early on, when no one understood the domain or the requirements very well.
不幸的是,这种演变意味着您的最终结构在开始时无法使用,这意味着您必须在进行过程中进行重构以实施它。这可能既昂贵又困难,但这是必要的。有一些通用方法可以控制成本并最大化收益。
Unfortunately, that evolution means that your final structure will not be available at the start, and that means that you will have to refactor to impose it as you go along. This can be expensive and difficult, but it is necessary. There are some general ways of controlling the cost and maximizing the gain.
降低成本的一个关键是保持结构简单和轻量。不要试图面面俱到。只需解决最严重的问题,其余问题则根据具体情况处理。
One key to keeping the cost down is to keep the structure simple and lightweight. Don’t attempt to be comprehensive. Just address the most serious concerns and leave the rest to be handled on a case-by-case basis.
在早期,选择一个松散的结构会很有帮助,比如系统隐喻或几个责任层。尽管如此,最小的松散结构仍然可以提供轻量级的指导方针,有助于防止混乱。
Early on, it can be helpful to choose a loose structure, such as a SYSTEM METAPHOR or a couple of RESPONSIBILITY LAYERS. A minimal, loose structure can nonetheless provide lightweight guidelines that will help prevent chaos.
整个团队在进行新开发和重构时都必须遵循该结构。为此,整个团队必须理解该结构。术语和关系必须进入 UBIQUITOUS LANGUAGE。
The entire team must follow the structure in new development and refactoring. To do this, the structure must be understood by the entire team. The terminology and relationships must enter the UBIQUITOUS LANGUAGE.
大规模结构可以为项目提供广泛处理系统的词汇,并为不同的人独立做出和谐的决策提供词汇。但由于大多数大规模结构都是松散的概念指导,因此团队必须自我约束。
Large-scale structure can provide a vocabulary for the project to deal with the system broadly, and for different people independently to make harmonious decisions. But because most large-scale structures are loose conceptual guidelines, the teams must exercise self-discipline.
如果参与的许多人不坚持一致,结构就会趋于衰败。结构与模型或实现的详细部分之间的关系通常不会在代码中明确说明,功能测试也不依赖于结构。此外,结构往往是抽象的,因此很难在大型团队(或多个团队)中保持应用的一致性。
Without consistent adherence by the many people involved, structures have a tendency to decay. The relationship of the structure to detailed parts of the model or implementation is not usually explicit in the code, and functional tests do not rely on the structure. Plus, the structure tends to be abstract, so that consistency of application can be difficult to maintain across a large team (or multiple teams).
大多数团队中进行的对话类型不足以维持系统中一致的大规模结构。将其纳入项目的通用语言中并让每个人都坚持不懈地练习这种语言至关重要。
The kinds of conversations that take place on most teams are not enough to maintain a consistent large-scale structure in a system. It is critical to incorporate it into the UBIQUITOUS LANGUAGE of the project, and for everyone to exercise that language relentlessly.
其次,任何结构变化都可能导致大量重构。随着系统复杂性的增加和理解的加深,结构也在不断演变。每次结构发生变化时,整个系统都必须改变以遵循新的顺序。显然,这是一项艰巨的工作。
Second, any change to the structure may lead to a lot of refactoring. The structure is evolving as system complexity increases and understanding deepens. Each time the structure changes, the entire system has to be changed to adhere to the new order. Obviously that is a lot of work.
这并不像听起来那么糟糕。我观察到,具有大规模结构的设计通常比没有大规模结构的设计更容易转变。即使从一种结构变为另一种结构,比如从METAPHOR变为LAYERS ,情况似乎也是如此。我无法完全解释这一点。部分答案是,当你能理解事物当前的排列时,重新排列它会更容易,而先前存在的结构会使这变得更容易。部分原因是,维护早期结构所需的纪律渗透到了系统的各个方面。但我认为,还有更多原因,因为改变一个有过两种先前结构的系统会更容易。
This isn’t quite as bad as it sounds. I’ve observed that a design with a large-scale structure is usually much easier to transform than one without. This seems to be true even when changing from one kind of structure to another, say from METAPHOR to LAYERS. I can’t entirely explain this. Part of the answer is that it is easier to rearrange something when you can understand its current arrangement, and the preexisting structure makes that easier. Partly it is that the discipline that it took to maintain the earlier structure permeates all aspects of the system. But there is something more, I think, because it is even easier to change a system that has had two previous structures.
一件新皮夹克很硬,穿起来不舒服,但穿了第一天后,肘部已经弯曲了几次,变得更容易了弯曲。再穿几次之后,肩膀就松了,夹克也更容易穿上了。穿了几个月之后,皮革变得柔软,穿着舒适,活动自如。通过声音变换反复变换的模型似乎也是如此。不断增加的知识嵌入其中,变化的主要轴线已经确定并变得灵活,而稳定的方面已经简化。底层领域更广泛的概念轮廓正在模型结构中出现。
A new leather jacket is stiff and uncomfortable, but after the first day of wear the elbows have flexed a few times and are becoming easier to bend. After a few more wearings, the shoulders have loosened up, and the jacket is easier to put on. After months of wear, the leather becomes supple and is comfortable and easy to move in. So it seems to be with models that are transformed repeatedly with sound transformations. Ever-increasing knowledge is embedded into them and the principal axes of change have been identified and made flexible, while stable aspects have been simplified. The broader CONCEPTUAL CONTOURS of the underlying domain are emerging in the model structure.
另一个应该应用于模型的关键力量是持续提炼。这降低了以各种方式改变结构的难度。首先,通过从核心域中删除机制、通用子域和其他支持结构,可能需要重组的内容可能会更少。
Another crucial force that should be applied to the model is continuous distillation. This reduces the difficulty of changing the structure in various ways. First, by removing mechanisms, GENERIC SUBDOMAINS, and other support structure from the CORE DOMAIN, there may simply be less to restructure.
如果可能的话,这些支持元素应该以一种简单的方式定义,以适应大规模结构。例如,在RESPONSIBILITY LAYERS系统中,GENERIC SUBDOMAIN可以这样定义,使其适合单层。使用PLUGGABLE COMPONENTS,GENERIC SUBDOMAIN可以完全由单个组件拥有,也可以是一组相关组件之间的SHARED KERNEL。这些支持元素可能必须重构才能在结构中找到它们的位置;但它们独立于CORE DOMAIN移动,并且往往更加集中,这使得它更容易。而且最终它们不那么重要,所以细化就不那么重要了。
If possible, these supporting elements should be defined to fit into the large-scale structure in a simple way. For example, in a system of RESPONSIBILITY LAYERS, a GENERIC SUBDOMAIN could be defined in such a way that it would fit within a single layer. With PLUGGABLE COMPONENTS, a GENERIC SUBDOMAIN could be owned entirely by a single component, or it could be a SHARED KERNEL among a set of related components. These supporting elements may have to be refactored to find their place in the structure; but they move independently of the CORE DOMAIN, and tend to be more narrowly focused, which makes it easier. And ultimately they are less critical, so refinement matters less.
提炼和重构以获得更深入见解的原则甚至适用于大型结构本身。例如,最初可能基于对领域的肤浅理解来选择层;它们逐渐被表达系统基本职责的更深层次的抽象所取代。这种清晰的清晰度让人们深入了解设计,这是目标。它也是手段的一部分,因为它使大规模系统的操作更容易、更安全。
The principles of distillation and refactoring toward deeper insight apply even to the large-scale structure itself. For example, the layers may initially be chosen based on a superficial understanding of the domain; they are gradually replaced with deeper abstractions that express the fundamental responsibilities of the system. This sharpedged clarity lets people see deep into the design, which is the goal. It is also part of the means, as it makes manipulation of the system on a large scale easier and safer.
前三章介绍了领域驱动战略设计的许多原则和技术。在大型复杂系统中,您可能需要将其中的几种应用于同一设计。大型结构如何与上下文图共存?构建块放在哪里?您首先做什么?第二?第三?您如何制定策略?
The preceding three chapters presented many principles and techniques for domain-driven strategic design. In a large, complex system, you may need to bring several of them to bear on the same design. How does a large-scale structure coexist with a CONTEXT MAP? Where do the building blocks fit in? What do you do first? Second? Third? How do you go about devising your strategy?
图 17.1
Figure 17.1
战略设计的三个基本原则(情境、提炼和大尺度结构)并不是互相替代的,它们是互补的,并且以多种方式相互作用。例如,大尺度结构可以存在于一个有界情境中,也可以跨越多个有界情境并组织情境图。
The three basic principles of strategic design (context, distillation, and large-scale structure) are not substitutes for each other; they are complementary and interact in many ways. For example, a large-scale structure can exist within one BOUNDED CONTEXT, or it can cut across many of them and organize the CONTEXT MAP.
先前的责任层示例仅限于一个有界上下文。这是解释该想法的最简单方法,也是该模式的常见用法。在这种简单的场景中,层名称的含义仅限于该上下文,存在于该上下文中的模型元素或子系统接口的名称也是如此。
The previous examples of RESPONSIBILITY LAYERS were confined to one BOUNDED CONTEXT. This is the easiest way to explain the idea, and it’s a common use of the pattern. In such a simple scenario, the meanings of layer names are restricted to that CONTEXT, as are the names of model elements or subsystem interfaces that exist within that CONTEXT.
图 17.2. 在单个BOUNDED CONTEXT中构建模型
Figure 17.2. Structuring a model within a single BOUNDED CONTEXT
这样的局部结构在非常复杂但统一的模型中很有用,它提高了在单个BOUNDED CONTEXT中可维护的复杂性上限。
Such a local structure can be useful in a very complicated but unified model, raising the complexity ceiling on how much can be maintained in a single BOUNDED CONTEXT.
但在许多项目中,更大的挑战是理解不同的部分如何组合在一起。它们可能被划分为单独的上下文,但每个部分在整个集成系统中扮演什么角色,以及各个部分如何相互关联?然后可以使用大型结构来组织上下文图。在这种情况下,结构的术语适用于整个项目(或至少是其中某些明确界定的部分)。
But on many projects, the greater challenge is to understand how disparate parts fit together. They may be partitioned into separate CONTEXTS, but what part does each play in the whole integrated system and how do the parts relate to each other? Then the large-scale structure can be used to organize the CONTEXT MAP. In this case, the terminology of the structure applies to the whole project (or at least some clearly bounded part of it).
图 17.3. 强加于不同有界上下文的组件关系的结构
Figure 17.3. Structure imposed on the relationships of components of distinct BOUNDED CONTEXTS
假设您想采用职责层,但您有一个遗留系统,其组织方式与您期望的大规模结构不一致。您是否必须放弃您的层?不,但您必须承认遗留系统在结构中的实际位置。事实上,它可能有助于描述遗留系统。遗留系统提供的服务实际上可能仅限于几个层。能够说遗留系统适合特定的职责层,简明扼要地描述了其范围和作用的一个关键方面。
Suppose you want to adopt RESPONSIBILITY LAYERS, but you have a legacy system whose organization is inconsistent with your desired large-scale structure. Do you have to give up your LAYERS? No, but you have to acknowledge the actual place the legacy has within the structure. In fact, it may help to characterize the legacy. The SERVICES the legacy provides may in fact be confined to only a few LAYERS. To be able to say that the legacy system fits within particular RESPONSIBILITY LAYERS concisely describes a key aspect of its scope and role.
图 17.4。允许一些组件跨越层的结构
Figure 17.4. A structure that allows some components to span layers
如果通过FACADE访问遗留子系统的功能,您可能能够将FACADE提供的每个服务设计为适合一个层。
If the legacy subsystem’s capabilities are being accessed through a FACADE, you may be able to design each SERVICE offered by the FACADE to fit within one layer.
在本例中,航运协调应用程序的内部是一个遗留问题,它被呈现为一个未分化的整体。但是,一个拥有完善的跨越上下文图的大规模结构的项目团队可以选择在他们的上下文中,按照同样熟悉的层来排列他们的模型。
The interior of the Shipping Coordination application, being a legacy in this example, is presented as an undifferentiated mass. But a team on a project with a well-established large-scale structure spanning the CONTEXT MAP could choose, within their CONTEXT, to order their model by the same familiar LAYERS.
图 17.5。在CONTEXT中以及整个CONTEXT MAP中应用相同的结构
Figure 17.5. The same structure applied within a CONTEXT and across the CONTEXT MAP as a whole
当然,因为每个BOUNDED CONTEXT都是其自己的名称空间,所以可以使用一个结构来组织一个CONTEXT中的模型,而另一个结构用于相邻的CONTEXT中,还有一个结构组织CONTEXT MAP。但是,如果走得太远,可能会削弱大型结构作为项目统一概念集的价值。
Of course, because each BOUNDED CONTEXT is its own name space, one structure could be used to organize the model within one CONTEXT, while another was used in a neighboring CONTEXT, and still another organized the CONTEXT MAP. However, going too far down that path can erode the value of the large-scale structure as a unifying set of concepts for the project.
大规模结构和提炼的概念也是相辅相成的。大规模结构可以帮助解释CORE DOMAIN内部以及GENERIC SUBDOMAINS之间的关系。
The concepts of large-scale structure and distillation also complement each other. The large-scale structure can help explain the relationships within the CORE DOMAIN and between GENERIC SUBDOMAINS.
图 17.6.核心域(粗体)和通用子域的模块按层进行划分。
Figure 17.6. MODULES of the CORE DOMAIN (in bold) and GENERIC SUBDOMAINS are clarified by the layers.
同时,大型结构本身可能是核心域的重要组成部分。例如,区分潜力、运营、政策和决策支持的层次可以提炼出对软件解决的业务问题至关重要的见解。如果项目被划分为许多有界上下文,那么这种见解尤其有用,因为核心域的模型对象在项目的大部分内容中都没有意义。
At the same time, the large-scale structure itself may be an important part of the CORE DOMAIN. For example, distinguishing the layering of potential, operations, policy, and decision support distills an insight that is fundamental to the business problem addressed by the software. This insight is especially useful if a project is carved up into many BOUNDED CONTEXTS, so that the model objects of the CORE DOMAIN don’t have meaning over much of the project.
当您处理项目的战略设计时,您需要从对当前情况的清晰评估开始。
When you are tackling strategic design on a project, you need to start from a clear assessment of the current situation.
1.画出上下文图。你能画出一致的上下文图吗,或者是否存在模棱两可的情况?
1. Draw a CONTEXT MAP. Can you draw a consistent one, or are there ambiguous situations?
2.注意项目中语言的使用。是否有通用语言?是否足够丰富以协助开发?
2. Attend to the use of language on the project. Is there a UBIQUITOUS LANGUAGE? Is it rich enough to help development?
3.了解什么是重要的。是否确定了核心领域?是否有领域愿景声明?你能写一个吗?
3. Understand what is important. Is the CORE DOMAIN identified? Is there a DOMAIN VISION STATEMENT? Can you write one?
4.该项目的技术是否支持或反对模型驱动设计?
4. Does the technology of the project work for or against a MODEL-DRIVEN DESIGN?
5.团队中的开发人员是否具备必要的技术技能?
5. Do the developers on the team have the necessary technical skills?
6.开发人员是否了解该领域?他们对该领域感兴趣吗?
6. Are the developers knowledgeable about the domain? Are they interested in the domain?
当然,你不会找到完美的答案。你现在对这个项目的了解比将来的了解要少。但这些问题为你提供了一个坚实的起点。当你对这些问题有了具体的初步答案时,你就会开始了解最迫切需要做的事情。随着时间的推移,你可以完善答案——尤其是上下文图、领域愿景声明和你创建的任何其他工件——以反映变化的情况和新的见解。
You won’t find perfect answers, of course. You know less about this project right now than you ever will in the future. But these questions give you a solid starting point. By the time you have specific initial answers to these questions, you’ll have started getting insight into what most urgently needs to be done. As time goes along, you can refine the answers—especially the CONTEXT MAP, DOMAIN VISION STATEMENT, and any other artifacts you’ve created—to reflect changed situations and new insights.
传统上,架构是在应用程序开发开始之前由组织中比应用程序开发团队拥有更大权力的团队制定的。但事实并非如此。这种方式通常效果不佳。
Traditionally, architecture is handed down, created before application development begins, by a team that has more power in the organization than the application development team. But it doesn’t have to be that way. That way doesn’t usually work very well.
从定义上讲,战略设计必须适用于整个项目。组织项目的方法有很多,我不想太过规范。但是,任何决策过程要想有效,都需要一些基本原则。
Strategic design, by definition, must apply across the project. There are many ways to organize a project, and I don’t want to be too prescriptive. However, for any decision-making process to be effective, some fundamentals are required.
首先,让我们快速看一下我所见过的在实践中具有一定价值的两种风格(因此忽略了旧的“自上而下的智慧”风格)。
First, let’s take a quick look at two styles that I’ve seen provide some value in practice (thus ignoring the old “wisdom-from-on-high” style).
一支由非常优秀的沟通者组成的自律团队可以在没有中央权威的情况下运作,并遵循不断发展的秩序来达成一套共同的原则,这样秩序就会有机地发展,而不是靠命令。
A self-disciplined team made up of very good communicators can operate without central authority and follow EVOLVING ORDER to arrive at a shared set of principles, so that order grows organically, not by fiat.
这是极限编程团队的典型模型。理论上,结构可能完全自发地从任何一对编程搭档的见解中产生。更常见的情况是,让团队中的某个人或某个小组负责监督大规模结构,有助于保持结构的统一。这种方法非常有效,特别是如果这种非正式领导者是一位亲力亲为的开发人员——仲裁者和沟通者,而不是唯一的创意来源。在我见过的极限编程团队中,这种战略设计领导力似乎是自发产生的,通常是教练。无论这个天生的领导者是谁,他或她仍然是开发团队的一员。因此,开发团队必须至少有几个有能力做出影响整个项目的设计决策的人。
This is the typical model for an Extreme Programming team. In theory, the structure may emerge completely spontaneously from the insight of any programming pair. More often, having an individual or a subset of the team with some oversight responsibility for large-scale structure helps keep the structure unified. This approach works well particularly if such an informal leader is a hands-on developer—an arbiter and communicator, and not the sole source of ideas. On the Extreme Programming teams I have seen, such strategic design leadership seems to have emerged spontaneously, often in the person of the coach. Whoever this natural leader is, he or she is still a member of the development team. It follows that the development team must have at least a few people of the caliber to make design decisions that are going to affect the whole project.
当大型结构跨越多个团队时,关系密切的团队可能会开始非正式合作。在这种情况下,每个应用团队仍然会发现导致大型结构想法的东西,但随后由各个团队的代表组成的非正式委员会会讨论具体选项。在评估设计的影响后,参与者可能会决定采用、修改或将其搁置。团队试图在这种松散的联系中一起行动。当团队数量相对较少、他们都致力于相互协调、他们的设计能力相当、他们的结构需求足够相似以至于可以通过一个大型结构来满足时,这种安排是可行的。
When a large-scale structure spans multiple teams, closely affiliated teams may begin to collaborate informally. In such a situation, each application team still makes the discoveries that lead to the idea for a large-scale structure, but then particular options are discussed by the informal committee, made up of representatives of the various teams. After assessing the impact of the design, participants may decide to adopt it, modify it, or leave it on the table. The teams attempt to move together in this loose affiliation. This arrangement can work when there are relatively few teams, when they are all committed to coordinating with each other, when their design capabilities are comparable, and when their structural needs are similar enough to be met by a single large-scale structure.
当多个团队共享一个策略时,某种程度的决策集中化确实看起来很有吸引力。失败的象牙塔架构师模式并不是唯一的可能性。架构团队可以充当各种应用程序团队的同行,帮助协调和统一他们的大规模结构以及有界上下文边界和其他跨团队技术问题。要做到这一点,他们必须具有强调应用程序开发的思维方式。
When a strategy will be shared among several teams, some centralization of decision making does seem attractive. The failed model of the ivory tower architect is not the only possibility. An architecture team can act as a peer with various application teams, helping to coordinate and harmonize their large-scale structures as well as BOUNDED CONTEXT boundaries and other cross-team technical issues. To be useful in this, they must have a mind set that emphasizes application development.
在组织结构图上,这个团队可能看起来就像传统的架构团队,但实际上它在每项活动中都有所不同。团队成员是真正的开发合作者,与开发人员一起发现模式,与各个团队一起进行实验以达到提炼,并亲自动手。
On an organization chart, this team may look just like the traditional architecture team, but it is actually different in every activity. Team members are true collaborators with development, discovering patterns along with the developers, experimenting with various teams to reach distillations, and getting their hands dirty.
我曾多次见过这种情况,一个项目最终由一位首席架构师负责下列列表中的大部分工作。
I have seen this scenario a couple of times, when a project ends up with a lead architect who does most of the things on the following list.
显然,如果每个人都不知道并遵循该策略,那么它就毫无意义。这一要求导致人们围绕具有官方“权威”的集中式架构团队进行组织——以便将相同的规则应用于所有地方。具有讽刺意味的是,象牙塔架构师经常被忽视或绕过。当架构师缺乏将自己的规则应用于实际应用程序的实际尝试的反馈导致方案不切实际时,开发人员别无选择。
Obviously, if everyone doesn’t know the strategy and follow it, it is irrelevant. This requirement leads people to organize around centralized architecture teams with official “authority”—so that the same rules will be applied everywhere. Ironically, ivory tower architects are often ignored or bypassed. Developers have no choice when the architects’ lack of feedback from hands-on attempts to apply their own rules to real applications results in impractical schemes.
在沟通非常顺畅的项目中,应用团队提出的战略设计实际上可能更有效地传达给每个人。该战略将具有相关性,并且具有与智能社区决策相关的权威性。
On a project with very good communication, a strategic design that emerges from the application team may actually reach everyone more effectively. The strategy will be relevant, and it will have the authority that attaches to intelligent community decisions.
无论是哪种系统,都不要太在意管理层赋予的权力,而要更关注开发人员与策略的实际关系。
Whatever the system, be less concerned with the authority bestowed by management than with the actual relationship the developers have with the strategy.
创建组织原则、大规模结构或提炼如此微妙的内容需要对项目需求和领域概念有非常深入的了解。唯一拥有这种深度知识的人是应用程序开发团队的成员。这解释了为什么架构团队创建的应用程序架构很少有用,尽管许多架构师都才华横溢。
Creating an organizing principle, large-scale structure, or distillation of such subtlety requires a really deep understanding of the needs of the project and the concepts of the domain. The only people who have that depth of knowledge are the members of the application development team. This explains why application architectures created by architecture teams are so seldom helpful, despite the undeniable talent of many of the architects.
与技术基础设施和架构不同,战略设计本身并不涉及编写大量代码,尽管它会影响所有开发。它需要的是应用程序开发团队的参与。经验丰富的架构师可能能够倾听来自各个团队的想法,并促进通用解决方案的开发。
Unlike technical infrastructure and architectures, strategic design does not itself involve writing a lot of code, although it influences all development. What it does require is involvement with the application development teams. An experienced architect may be able to listen to ideas coming from various teams and facilitate the development of a generalized solution.
我曾与一个技术架构团队合作,他们让团队成员在尝试使用其框架的各个应用程序开发团队中轮换。这种轮换让架构团队获得了开发人员面临的挑战的实际经验,同时也传授了如何应用框架的精妙之处的知识。战略设计同样需要紧密的反馈循环。
One technical architecture team I worked with actually circulated its own members through the various application development teams that were attempting to use its framework. This rotation pulled into the architecture team the hands-on experience of the challenges facing the developers, while it simultaneously transferred the knowledge of how to apply the subtleties of the framework. Strategic design has this same need of a tight feedback loop.
有效的软件开发是一个高度动态的过程。当最高级别的决策一成不变时,团队在必须应对变化时的选择就会减少。E VOLVING ORDER强调随着洞察力的加深而对大规模结构进行持续的改变,从而避免了这一陷阱。
Effective software development is a highly dynamic process. When the highest level of decisions is set in stone, the team has fewer options when it must respond to change. EVOLVING ORDER avoids this trap by emphasizing ongoing change to the large-scale structure in response to deepening insight.
当太多的设计决策是预先确定的,开发团队就会受到束缚,无法灵活地解决他们面临的问题。因此,虽然协调原则很有价值,但它必须随着开发项目的持续发展而发展和变化,并且不能剥夺应用程序开发人员太多的权力,因为他们的工作已经够难了。
When too many design decisions are preordained, the development team can be hobbled, without the flexibility to solve the problems they are charged with. So, while a harmonizing principle can be valuable, it must grow and change with the ongoing life of the development project, and it must not take too much power away from the application developers, whose job is hard enough as it is.
在强有力的反馈下,当构建应用程序时遇到障碍以及发现意外机会时,创新就会出现。
With strong feedback, innovations emerge as obstacles are encountered in building applications and as unexpected opportunities are discovered.
这一级别的设计要求的复杂性可能非常稀缺。管理人员倾向于将技术上最有天赋的开发人员调入架构团队和基础设施团队,因为他们想利用这些高级设计师的技能。对于开发人员而言,他们被产生更广泛影响或研究“更有趣”问题的机会所吸引。成为精英团队的一员也是一种荣誉。
Design at this level calls for sophistication that is probably in short supply. Managers tend to move the most technically talented developers into architecture teams and infrastructure teams, because they want to leverage the skills of these advanced designers. For their part, the developers are attracted to the opportunity to have a broader impact or to work on “more interesting” problems. And there is prestige attached to being a member of an elite team.
这些因素往往导致技术水平最低的开发人员无法真正开发应用程序。但开发优秀的应用程序需要设计技巧;这注定会失败。即使战略团队创建了出色的战略设计,应用程序团队也不会具备足够的设计水平来跟进。
These forces often leave behind only the least technically sophisticated developers to actually build applications. But building good applications takes design skill; this is a setup for failure. Even if a strategy team creates a great strategic design, the application team won’t have the design sophistication to follow it.
相反,这样的团队几乎从不包含那些设计技能可能较弱但在该领域拥有最丰富经验的开发人员。战略设计不是一项纯粹的技术任务;与拥有深厚领域知识的开发人员断绝联系会进一步阻碍架构师的努力。而且也需要领域专家。
Conversely, such teams almost never include the developer who perhaps has weaker design skills but who has the most extensive experience in the domain. Strategic design is not a purely technical task; cutting themselves off from developers with deep domain knowledge hobbles the architects’ efforts further. And domain experts are needed too.
所有应用团队都必须拥有强大的设计师。任何尝试战略设计的团队都必须具备领域知识。可能只需要聘请更高级的设计师。这可能有助于让架构团队保持兼职状态。我相信有很多方法可以奏效,但任何有效的战略团队都必须有一个有效的应用团队作为合作伙伴。
It is essential to have strong designers on all application teams. It is essential to have domain knowledge on any team attempting strategic design. It may simply be necessary to hire more advanced designers. It may help to keep architecture teams part-time. I’m sure there are many ways that work, but any effective strategy team has to have as a partner an effective application team.
提炼和极简主义对于任何优秀的设计工作都是必不可少的,但极简主义对于战略设计来说更为关键。即使是最轻微的不合适也有可能成为阻碍。独立的架构团队必须特别小心,因为他们对可能摆在应用程序团队面前的障碍没有太多的感觉。同时,架构师对其主要职责的热情使他们更容易得意忘形。我见过这种现象很多次,甚至自己也做过。一个好主意会引发另一个好主意,最终我们得到的是过度构建的架构,这适得其反。
Distillation and minimalism are essential to any good design work, but minimalism is even more critical for strategic design. Even the slightest ill fit has a terrible potential for getting in the way. Separate architecture teams have to be especially careful because they have less feel for the obstacles they might be placing in front of application teams. At the same time, the architects’ enthusiasm for their primary responsibility makes them more likely to get carried away. I’ve seen this phenomenon many times, and I’ve even done it. One good idea leads to another, and we end up with an overbuilt architecture that is counterproductive.
相反,我们必须严格要求自己制定组织原则和核心模型,精简到不包含任何不能显著提高设计清晰度的内容。事实是,几乎所有东西都会妨碍某事,所以每个元素最好都是值得的。意识到你最好的想法很可能会妨碍别人需要谦逊。
Instead, we have to discipline ourselves to produce organizing principles and core models that are pared down to contain nothing that does not significantly improve the clarity of the design. The truth is, almost everything gets in the way of something, so each element had better be worth it. Realizing that your best idea is likely to get in somebody’s way takes humility.
良好的对象设计本质是赋予每个对象明确而狭窄的职责,并将相互依赖性降至最低。有时,我们会尝试使团队中的交互尽可能整洁,就像软件中应该的那样。一个好的项目会让很多人插手别人的事情。开发人员使用框架。架构师编写应用程序代码。每个人都与每个人交谈。这是一种高效的混乱。让对象成为专家;让开发人员成为通才。
The essence of good object design is to give each object a clear and narrow responsibility and to reduce interdependence to an absolute minimum. Sometimes we try to make interactions on teams as tidy as they should be in our software. A good project has lots of people sticking their nose in other people’s business. Developers play with frameworks. Architects write application code. Everyone talks to everyone. It is efficiently chaotic. Make the objects into specialists; let the developers be generalists.
因为我区分了战略设计和其他类型的设计,以帮助澄清所涉及的任务,所以我必须指出,有两种设计活动并不意味着有两种人。基于深度模型创建柔性设计是一项高级设计活动,但细节非常重要,因此必须由使用代码的人来完成。战略设计源于应用程序设计,但它需要对活动的全局视角,可能涉及多个团队。人们喜欢想方设法将任务分解,这样设计专家就不必了解业务,领域专家也不必了解技术。一个人能学到的东西是有限的,但过度专业化会让领域驱动设计失去动力。
Because I’ve made the distinction between strategic design and other kinds of design to help clarify the tasks involved, I must point out that having two kinds of design activity does not mean having two kinds of people. Creating a supple design based on a deep model is an advanced design activity, but the details are so important that it has to be done by someone working with the code. Strategic design emerges out of application design, yet it requires a big-picture view of activity, possibly spanning multiple teams. People love to find ways to chop up tasks so that design experts don’t have to know the business and domain experts don’t have to understand technology. There is a limit to how much an individual can learn, but overspecialization takes the steam out of domain-driven design.
技术框架可以大大加速应用程序开发,包括领域层,因为它提供了一个基础设施层,使应用程序无需实现基本服务,并有助于将领域与其他问题隔离开来。但存在一种风险,即架构可能会干扰领域模型的表达性实现和轻松更改。即使框架设计者无意涉足领域或应用层。
Technical frameworks can greatly accelerate application development, including the domain layer, by providing an infrastructure layer that frees the application from implementing basic services, and by helping to isolate the domain from other concerns. But there is a risk that an architecture can interfere with expressive implementations of the domain model and easy change. This can happen even when the framework designers had no intention of venturing into the domain or application layers.
限制战略设计弊端的偏见同样有助于技术架构。演进、简约和应用程序开发团队的参与可以带来一套不断完善的服务和规则,真正帮助应用程序开发而不会妨碍应用程序开发。不遵循这条道路的架构要么会扼杀应用程序开发的创造力,要么会发现他们的架构被绕过,从实际目的来看,应用程序开发根本没有架构。
The same biases that limit the downside of strategic design can help with technical architecture. Evolution, minimalism, and involvement with the application development team can lead to a continuously refined set of services and rules that genuinely help application development without getting in the way. Architectures that don’t follow this path will either stifle the creativity of application development or will find their architecture circumvented, leaving application development, for practical purposes, with no architecture at all.
有一种特殊的态度肯定会毁掉一个框架。
There is one particular attitude that will surely ruin a framework.
团队部门如果认为某些开发人员不够聪明,无法进行设计,那么他们很可能会失败,因为他们低估了应用程序开发的难度。如果这些人不够聪明,无法进行设计,就不应该指派他们开发软件。如果他们足够聪明,那么纵容他们的尝试只会在他们和他们所需的工具之间设置障碍。
Team divisions that assume some developers are not smart enough to design are likely to fail because they underestimate the difficulty of application development. If those people are not smart enough to design, they shouldn’t be assigned to develop software. If they are smart enough, then the attempts to coddle them will only put up barriers between them and the tools they need.
这种态度也毒害了团队之间的关系。我曾经加入过这种傲慢的团队,每次谈话我都不得不向开发人员道歉,为自己的所作所为感到尴尬。(恐怕我从来没有成功改变过这样的团队。)
This attitude also poisons the relationship between teams. I’ve ended up on arrogant teams like this and found myself apologizing to developers in every conversation, embarrassed by my association. (I’ve never managed to change such a team, I’m afraid.)
现在,封装不相关的技术细节与我所鄙视的那种预先打包完全不同。框架可以将强大的抽象和工具交到开发人员手中,使他们从繁重的工作中解脱出来。很难用概括的方式来描述这种差异,但你可以通过询问框架设计者他们对将要使用该工具/框架/组件的人有什么期望来判断这种差异。如果设计者似乎对框架的用户有很高的尊重,那么他们可能走在正确的轨道上。
Now, encapsulating irrelevant technical detail is completely different from the kind of prepackaging I’m disparaging. A framework can place powerful abstractions and tools in developers’ hands and free them from drudgery. It is hard to describe the difference in a generalized way, but you can tell the difference by asking the framework designers what they expect of the person who will be using the tool/framework/components. If the designers seem to have a high level of respect for the user of the framework, then they are probably on the right track.
一群建筑师(设计实体建筑的那种),由克里斯托弗·亚历山大 (Christopher Alexander) 领导,提倡逐步发展建筑和城市规划领域。他们很好地解释了总体规划为何会失败。
A group of architects (the kind who design physical buildings), led by Christopher Alexander, were advocates of piecemeal growth in the realm of architecture and city planning. They explained very nicely why master plans fail.
如果没有某种规划过程,俄勒冈大学就不可能拥有像剑桥大学那样深厚而和谐的秩序。
Without a planning process of some kind, there is not a chance in the world that the University of Oregon will ever come to possess an order anywhere near as deep and harmonious as the order that underlies the University of Cambridge.
总体规划是解决这一难题的传统方法。总体规划试图制定足够的指导方针,以确保整个环境的连贯性——同时仍为个别建筑和开放空间留出自由,以适应当地需求。
The master plan has been the conventional way of approaching this difficulty. The master plan attempts to set down enough guidelines to provide for coherence in the environment as a whole—and still leave freedom for individual buildings and open spaces to adapt to local needs.
...而这所未来大学的各个部分将形成一个连贯的整体,因为它们只是被插入了设计的插槽中。
. . . and all the various parts of this future university will form a coherent whole, because they were simply plugged into the slots of the design.
……总体规划在实践中失败了——因为它们创造的是极权秩序,而不是有机秩序。它们过于僵化;它们无法轻易适应社区生活中不可避免地出现的自然和不可预测的变化。随着这些变化的发生……总体规划变得过时,不再被遵循。即使总体规划得到遵循……它们也没有充分说明建筑物之间的联系、人性化尺度、平衡功能等,以帮助每个局部建筑和设计行为与整个环境紧密相关。
. . . in practice master plans fail—because they create totalitarian order, not organic order. They are too rigid; they cannot easily adapt to the natural and unpredictable changes that inevitably arise in the life of a community. As these changes occur . . . the master plan becomes obsolete, and is no longer followed. And even to the extent that master plans are followed . . . they do not specify enough about connections between buildings, human scale, balanced function, etc. to help each local act of building and design become well-related to the environment as a whole.
...试图掌控这样的进程就像在孩子的图画书里填色一样......这个过程所产生的秩序充其量是平庸的。
. . . The attempt to steer such a course is rather like filling in the colors in a child’s coloring book . . . . At best, the order which results from such a process is banal.
……因此,作为有机秩序的源泉,总体规划既过于精确,又不够精确。总体过于精确,细节不够精确。
. . . Thus, as a source of organic order, a master plan is both too precise, and not precise enough. The totality is too precise: the details are not precise enough.
...总体规划的存在会疏远用户,[因为根据定义]社区成员对社区未来形态的影响很小,因为大多数重要决定已经做出。
. . . the existence of a master plan alienates the users [because, by definition] the members of the community can have little impact on the future shape of their community because most of the important decisions have already been made.
——摘自《俄勒冈实验》,第 16-28 页(Alexander 等人,1975 年)
—From The Oregon Experiment, pp. 16–28 (Alexander et al. 1975)
亚历山大和他的同事们提倡所有社区成员在每一个渐进式增长的行为中应用一套原则,这样就能形成适应环境的“有机秩序”。
Alexander and his colleagues advocated instead a set of principles for all community members to apply to every act of piecemeal growth, so that “organic order” emerges, well adapted to circumstances.
虽然从事前沿项目并尝试有趣的想法和工具非常令人满意,但对我来说,如果软件没有得到有效的利用,那么这种体验就毫无意义。事实上,成功的真正考验是软件在一段时间内的表现。多年来,我一直能够关注我以前参与的一些项目的故事。
Although it is very satisfying working on a cutting-edge project and experimenting with interesting ideas and tools, for me it is a hollow experience if the software does not find productive use. In fact, the true test of success is how the software serves over a period of time. I have been able to follow the stories of some of my former projects over the years.
我将在这里讨论其中的五个,每个项目都认真尝试了领域驱动设计,当然,它们不是系统性的,也不是以这个名字命名的。所有这些项目都交付了软件:一些项目成功实施并产生了模型驱动设计,而一个项目偏离了轨道。一些应用程序持续增长和变化了很多年,而一个应用程序停滞不前,一个应用程序早早夭折。
I’ll discuss here five of those, each of which made a serious attempt at domain-driven design, though not systematically and not by that name, of course. All of these projects did deliver software: some managed to carry through and produce a model-driven design, while one slipped off that track. Some of the applications continued to grow and change for many years, while one stagnated and one died young.
第 1 章中描述的 PCB 设计软件在该领域的测试版用户中大获成功。不幸的是,发起该项目的初创公司在营销方面彻底失败,最终被淘汰。该软件现在由少数 PCB 工程师使用,他们保留了测试版程序的旧副本。与任何孤儿软件一样,它将继续工作,直到与它集成的程序之一发生致命变化。
The PCB design software described in Chapter 1 was a smash hit among beta users in the field. Unfortunately, the start-up company that had initiated the project utterly failed in its marketing function and was eventually euthanized. The software is now used by a handful of PCB engineers who have old copies they kept from the beta program. Like any orphan software, it will continue to work until there is some fatal change to one of the programs with which it is integrated.
第九章中讲述的贷款软件在我所写的突破之后的三年里蓬勃发展,并沿着相同的轨迹发展。那时,该项目被分拆为独立公司。在这次重组的混乱中,从一开始就领导该项目的项目经理被赶下台,一些核心开发人员也随之离开。新团队的设计理念略有不同,没有完全致力于对象建模。但他们保留了一个具有复杂行为的独特领域层,并继续重视开发团队的领域知识。分拆七年后,该软件继续通过新功能得到增强。它是该领域的领先应用程序,为越来越多的客户机构提供服务,同时也是公司最大的收入来源。
The loan software whose story was told in Chapter 9 thrived and evolved along much the same track for three years after the breakthrough I wrote about. At that point, the project was spun off as an independent company. In the turmoil of this reorganization, the project manager who had led the project from the beginning was ejected, and some of the core developers left with him. The new team had a somewhat different design philosophy, not as fully committed to object modeling. But they retained a distinct domain layer with complex behavior and continued to value domain knowledge on the development team. Seven years after the spin-off, the software continues to be enhanced with new features. It is the leading application in its field and serves an increasing number of client institutions, as well as being the largest revenue stream for the company.
新种植的橄榄树
A Newly Planted Olive Grove
在领域驱动方法得到更广泛应用之前,许多项目中的有趣软件将在短时间内高效构建。最终,项目将转变为更常规的东西,可能无法充分利用、更不用说增强之前提炼的深度模型的功能。我本来可以期待更多,但这些确实是多年来为用户带来持续价值的成功。
Until the domain-driven approach is more widespread, the interesting software on many projects will be built in a short, highly productive interval. Eventually the project will transform into something more conventional that may not be able to fully exploit, much less enhance, the power of the deep models that were distilled earlier. I could wish for more, but truly those are successes that deliver sustained value to users over many years.
在一个项目中,我与另一位开发人员合作编写了一个实用程序,客户需要用它来生产其核心产品。这些功能相当复杂,并以错综复杂的方式组合在一起。我喜欢这个项目的工作,我们用抽象核心制作了一个灵活的设计。当这个软件交付时,最初开发它的每个人都结束了参与。因为它是如此突然的转变,我预计支持可组合元素的设计功能可能会令人困惑,可能会被更典型的案例逻辑取代。但这并没有发生。当我们交接时,该软件包包含一个完整的测试套件和一个提炼文档。新团队成员使用该文档来指导他们的探索,当他们研究事物时,他们对设计呈现的可能性感到兴奋。当我一年后听到他们的评论时,我意识到 UBIQUITOUS LANGUAGE已经激发了另一个团队并保持活力,继续发展。
On one project I paired with another developer to write a utility the customer needed to produce its core product. The features were fairly complicated and combined in intricate ways. I enjoyed the project work and we produced a supple design with an ABSTRACT CORE. When this software was handed off, that was the end of involvement for everyone who had initially developed it. Because it was such an abrupt transition, I expected that the design features which supported the combinable elements might be confusing and might get replaced by more typical case logic. This did not initially happen. When we handed off, the package included a thorough test suite and a distillation document. The new team members used that document to guide their explorations, and as they looked into things, they became excited by the possibilities the design presented. When I heard their comments a year later, I realized that the UBIQUITOUS LANGUAGE had sparked across to the other team and stayed alive, continuing to evolve.
七年后
Seven Years Later
一年后,我听到了另一个故事。团队遇到了新的需求,开发人员认为在继承的设计中没有任何方法可以实现这些需求。他们被迫将设计改得面目全非。当我进一步探究更多细节时,我发现我们模型的某些方面会让解决这些问题变得很棘手。正是在这样的时刻,往往有可能突破更深层次的模型,尤其是在这种情况下,开发人员已经积累了该领域的深厚知识和经验。事实上,他们涌现出大量新见解,并最终根据这些见解改变了模型和设计。
Then, another year later, I heard a different story. The team had encountered new requirements that the developers didn’t see any way to accomplish within the inherited design. They had been forced to change the design almost beyond recognition. As I probed for more details, I could see that aspects of our model would have made solving those problems awkward. It is precisely during such moments when a breakthrough to a deeper model is often possible, especially when, as in this case, the developers had accumulated deep knowledge and experience in the domain. In fact, they had had a rush of new insights and ended up transforming the model and design based on those insights.
他们小心翼翼地、圆滑地向我讲述了这个故事,我想,他们以为我会对他们丢弃我这么多作品感到失望。我对我的设计并不那么感伤。设计的成功不一定以它的停滞不前为标志。拿一个人们所依赖的系统,让它变得不透明,它将永远作为不可触碰的遗产存在。深度模型允许清晰的视野,从而产生新的见解,而灵活的设计则促进持续的变化。他们提出的模型更深入,更符合用户的实际关切。他们的设计解决了实际问题。软件的本质是变化,这个程序在拥有它的团队手中不断发展。
They told me this story carefully, diplomatically, expecting, I suppose, that I would be disappointed by their discarding of so much of my work. I am not that sentimental about my designs. The success of a design is not necessarily marked by its stasis. Take a system people depend on, make it opaque, and it will live forever as untouchable legacy. A deep model allows clear vision that can yield new insight, while a supple design facilitates ongoing change. The model they came up with was deeper, better aligned with the real concerns of the users. Their design solved real problems. It is the nature of software to change, and this program has continued to evolve in the hands of the team that owns it.
本书中散布的航运示例大致基于一家大型国际集装箱运输公司的项目。早期,该项目的领导层致力于领域驱动方法,但他们从未形成能够完全支持该方法的开发文化。几个设计技能和对象经验水平差异很大的团队着手创建模块,通过团队领导之间的非正式合作和以客户为中心的架构团队进行松散协调。我们确实开发了一个相当深的CORE DOMAIN模型,并且有一个可行的UBIQUITOUS LANGUAGE。
The shipping examples scattered through the book are loosely based on a project for a major international container-shipping company. Early on, the leadership of the project was committed to a domain-driven approach, but they never produced a development culture that could fully support it. Several teams with widely different levels of design skill and object experience set out to create modules, loosely coordinated by informal cooperation between team leaders and by a customer-focused architecture team. We did develop a reasonably deep model of the CORE DOMAIN, and there was a viable UBIQUITOUS LANGUAGE.
但公司文化强烈抵制迭代开发,我们等了太久才推出一个有效的内部版本。因此,问题在后期才暴露出来,那时修复这些问题的风险更大,成本也更高。在某个时候,我们发现模型的某些方面导致了数据库的性能问题。模型驱动设计的一个自然组成部分是从实施问题到模型变化的反馈,但那时人们认为我们已经走得太远,无法改变基本模型。相反,我们对代码进行了更改以使其更高效,并且它与模型的联系被削弱了。初始版本还暴露了技术基础设施的扩展限制,这让管理层感到恐慌。我们引入了专业知识来修复基础设施问题,项目恢复了正常。但实施和领域建模之间的循环从未关闭。
But the company culture fiercely resisted iterative development, and we waited far too long to push out a working internal release. Therefore, problems were exposed at a late stage, when they were more risky and expensive to fix. At some point, we discovered specific aspects of the model were causing performance problems in the database. A natural part of MODEL-DRIVEN DESIGN is the feedback from implementation problems to changes in the model, but by that time there was a perception that we were too far down the road to change the fundamental model. Instead, changes were made to the code to make it more efficient, and its connection to the model was weakened. The initial release also exposed scaling limitations in the technical infrastructure that threw a scare into management. Expertise was brought in to fix the infrastructure problems, and the project bounced back. But the loop was never closed between implementation and domain modeling.
少数团队交付了功能复杂、模型丰富的优秀软件。其他团队交付了僵硬的软件,将模型简化为数据结构,尽管他们保留了UBIQUITOUS LANGUAGE的痕迹。也许CONTEXT MAP对我们帮助最大,因为各个团队的输出之间的关系是杂乱无章的。然而,UBIQUITOUS LANGUAGE所包含的CORE模型确实帮助团队最终将系统粘合在一起。
A few teams delivered fine software with complex capabilities and expressive models. Others delivered stiff software that reduced the model to data structures, though even they retained traces of the UBIQUITOUS LANGUAGE. Perhaps a CONTEXT MAP would have helped us as much as anything, as the relationship between the output of the various teams was haphazard. Yet that CORE model carried in the UBIQUITOUS LANGUAGE did help the teams ultimately to glue together a system.
尽管范围缩小,但该项目取代了几个遗留系统。整体由一套共享的概念组成,尽管大多数设计都不太灵活。多年后的今天,它本身已基本固化为遗留系统,但它仍然全天候为全球业务服务。虽然更成功的团队的影响力逐渐扩大,但时间最终会耗尽,即使是在最富有的公司也是如此。该项目的文化从未真正吸收过模型驱动设计。如今的新开发是在不同的平台上进行的,并且只是间接地受到我们所做工作的影响——因为新开发人员会遵循他们的遗留系统。
Although reduced in scope, the project replaced several legacy systems. The whole was held together by a shared set of concepts, though most of the design was not very supple. It has itself largely fossilized into legacy now, years later, but it still serves the global business 24 hours a day. Although the more successful teams’ influence gradually spread, time runs out eventually, even in the richest company. The culture of the project never really absorbed MODEL-DRIVEN DESIGN. New development today is on different platforms and is only indirectly influenced by the work we did—as the new developers CONFORM to their legacy.
在某些圈子里,像航运公司最初设定的那些雄心勃勃的目标已经遭到了质疑。看起来,我们最好制作一些我们知道如何交付的小应用程序。最好坚持使用设计的最低标准来做简单的事情。这种保守的方法有其适用之处,并且可以实现范围明确、响应迅速的项目。但是,集成的模型驱动系统可以带来那些拼凑物无法实现的价值。还有第三种方法。领域驱动设计通过建立在深度模型和灵活设计的基础上,允许具有丰富功能的大型系统逐步发展。
In some circles, ambitious goals like those the shipping company initially set have been discredited. Better, it seems, to make little applications we know how to deliver. Better to stick to the lowest common denominator of design to do simple things. This conservative approach has its place, and allows for neatly scoped, quick-response projects. But integrated, model-driven systems promise value that those patchworks can’t. There is a third way. Domain-driven design allows piecemeal growth of big systems with rich functionality, by building on a deep model and supple design.
最后,我要以 Evant 结束这份名单,这是一家开发库存管理软件的公司,我在其中扮演了次要的支持角色,并为已经很强大的设计文化做出了贡献。其他人将这个项目描述为极限编程的典型代表,但通常没有注意到的是,这个项目是高度领域驱动的。更深层次的模型被提炼出来,并以越来越灵活的设计表达出来。这个项目一直蓬勃发展,直到 2001 年“互联网”泡沫破灭。后来,由于缺乏投资资金,公司收缩了业务,软件开发基本处于停滞状态,似乎末日即将来临。但在 2002 年夏天,全球十大零售商之一与 Evant 接洽。这个潜在客户喜欢这个产品,但需要更改设计,以便应用程序能够扩展以适应庞大的库存计划操作。这是 Evant 的最后机会。
I’ll close this list with Evant, a company that develops inventory management software, where I played a secondary supporting role and contributed to an already strong design culture. Others have written about this project as a poster child of Extreme Programming, but what is not usually remarked upon is that the project was intensely domain-driven. Ever deeper models were distilled and expressed in ever more supple designs. This project thrived until the “dot com” crash of 2001. Then, starved for investment funds, the company contracted, software development went mostly dormant, and it seemed that the end was near. But in the summer of 2002, Evant was approached by one of the top ten retailers in the world. This potential client liked the product, but it needed design changes to allow the application to scale up for an enormous inventory planning operation. It was Evant’s last chance.
尽管开发人员减少到四名,但团队还是有资产的。他们技术娴熟,熟悉该领域,其中一名成员在扩展问题方面拥有专业知识。他们拥有非常有效的开发文化。他们拥有一个设计灵活的代码库,可以促进变更。那年夏天,这四名开发人员付出了巨大的开发努力,最终能够处理数十亿个规划元素和数百名用户。凭借这些能力,Evant 赢得了这个庞大的客户,不久之后,它被另一家公司收购,这家公司希望利用他们的软件和经过验证的满足新需求的能力。
Although reduced to four developers, the team had assets. They were skilled, with knowledge of the domain, and one member had expertise in scaling issues. They had a very effective development culture. And they had a code base with a supple design that facilitated change. That summer, those four developers made a heroic development effort resulting in the ability to handle billions of planning elements and hundreds of users. On the strength of those capabilities, Evant won the behemoth client and, soon after, was bought by another company that wanted to leverage their software and their proven ability to accommodate new demands.
领域驱动设计文化(以及极限编程文化)在转型中幸存下来并重新焕发活力。如今,模型和设计仍在不断发展,比我做出贡献时已经过去了两年,现在更加丰富和灵活。而且,Evant 的成员并没有被同化到采购公司,而是团队似乎正在激励公司现有的项目团队效仿他们的做法。这个故事还没有结束。
The domain-driven design culture (as well as the Extreme Programming culture) survived the transition and was revitalized. Today, the model and design continue to evolve, far richer and suppler two years later than when I made my contribution. And rather than being assimilated into the purchasing company, the members of the Evant team seem to be inspiring the company’s existing project teams to follow their lead. This story isn’t over yet.
没有任何项目会采用本书中的所有技术。即便如此,任何致力于领域驱动设计的项目都会在某些方面被识别出来。其决定性特征是优先理解目标领域并将这种理解融入到软件中。其他一切都源于这个前提。团队成员意识到项目中语言的使用并培养其细化。他们很难对领域模型的质量感到满意,因为他们会继续学习更多关于领域的知识。他们将持续的细化视为机遇,将不合适的模型视为风险。他们非常重视设计技巧,因为开发出能够清晰反映领域模型的生产质量软件并不容易。他们跌倒在障碍上,但他们坚持自己的原则,站起来继续前进。
No project will ever employ every technique in this book. Even so, any project committed to domain-driven design will be recognizable in a few ways. The defining characteristic is a priority on understanding the target domain and incorporating that understanding into the software. Everything else flows from that premise. Team members are conscious of the use of language on the project and cultivate its refinement. They are hard to satisfy with the quality of the domain model, because they keep learning more about the domain. They see continuous refinement as an opportunity and an ill-fitting model as a risk. They take design skill seriously because it isn’t easy to develop production-quality software that clearly reflects the domain model. They stumble over obstacles, but they hold on to their principles as they pick themselves up and continue forward.
与物理学或化学相比,天气、生态系统和生物学过去被认为是混乱的“软”领域。然而,最近人们已经认识到,“混乱”的出现实际上对发现和理解这些非常复杂现象中的秩序提出了巨大的技术挑战。被称为“复杂性”的领域是许多科学的先锋。虽然纯技术任务对于有才华的软件工程师来说通常看起来最有趣和最具挑战性,但领域驱动设计开辟了一个至少具有同等挑战性的新领域。商业软件不必是一团乱麻。将复杂的领域转化为可理解的软件设计对于技术娴熟的人来说是一项令人兴奋的挑战。
Weather, ecosystems, and biology used to be considered messy, “soft” fields in contrast to physics or chemistry. Recently, however, people have recognized that the appearance of “messiness” in fact presents a profound technical challenge to discover and understand the order in these very complex phenomena. The field called “complexity” is the vanguard of many sciences. Although purely technological tasks have generally seemed most interesting and challenging to talented software engineers, domain-driven design opens up a new area of challenge that is at least equal. Business software does not have to be a bolted-together mess. Wrestling a complex domain into a comprehensible software design is an exciting challenge for strong technical people.
我们离非专业人士开发复杂软件的时代还很远。大批只具备基本技能的程序员可以开发某些类型的软件,但无法开发出在最后关头拯救公司的软件。工具制造商需要专注于扩展优秀软件开发人员的能力和生产力。需要的是更敏锐的方法来探索领域模型并在工作软件中表达它们。我期待尝试为此目的设计的新工具和技术。
We are nowhere near the era of laypeople creating complex software that works. Armies of programmers with rudimentary skills can produce certain kinds of software, but not the kind that saves a company in its eleventh hour. What is needed is for tool builders to put their minds to the task of extending the power and productivity of talented software developers. What is needed are sharper ways of exploring domain models and expressing them in working software. I look forward to experimenting with new tools and technologies devised for this purpose.
但尽管改进的工具很有价值,我们也不能因此而分心,而忽视了创建优秀软件是一项学习和思考活动这一核心事实。建模需要想象力和自律。帮助我们思考或避免分心的工具是好的。试图将思维的产物自动化是幼稚且适得其反的。
But though improved tools will be valuable, we mustn’t get distracted by them and lose sight of the core fact that creating good software is a learning and thinking activity. Modeling requires imagination and self-discipline. Tools that help us think or avoid distraction are good. Efforts to automate what must be the product of thought are naive and counterproductive.
利用我们已有的工具和技术,我们可以构建比当今大多数项目更有价值的系统。我们可以编写出使用起来和工作起来都令人愉悦的软件,这种软件在发展过程中不会束缚我们,而是创造新的机会并不断为其所有者增加价值。
With the tools and technology we already have, we can build systems much more valuable than most projects do today. We can write software that is a pleasure to use and a pleasure to work on, software that doesn’t box us in as it grows but creates new opportunities and continues to add value for its owners.
我的第一辆“好车”是大学毕业后不久得到的,是一辆八年前的标致。这辆车有时被称为“法国奔驰”,做工精良,驾驶起来很愉快,而且非常可靠。但当我拿到它的时候,它已经到了开始出问题、需要更多维护的年龄了。
My first “nice car,” which I was given shortly after college, was an eight-year-old Peugeot. Sometimes called the “French Mercedes,” this car was well crafted, was a pleasure to drive, and had been very reliable. But by the time I got it, it was reaching the age when things start to go wrong and more maintenance is required.
标致是一家老牌公司,几十年来一直走着自己的发展道路。它有自己的机械术语,设计也独具特色;甚至功能分解成零件有时也是不标准的。结果就是只有标致专家才能修车,这对靠研究生收入的人来说是个潜在的问题。
Peugeot is an old company, and it has followed its own evolutionary path over many decades. It has its own mechanical terminology, and its designs are idiosyncratic; even the breakdown of functions into parts is sometimes nonstandard. The result is a car that only Peugeot specialists can work on, a potential problem for someone on a grad student income.
有一次,我把车送到当地的机械师那里检查油漏。他检查了底盘,告诉我油“从后面三分之二处的小盒子里漏出来,这似乎与前后轮之间的制动力分配有关。”然后他拒绝碰车,建议我去五十英里外的经销商那里。任何人都可以修理福特或本田;这就是为什么这些车更方便、更便宜,尽管它们的机械结构同样复杂。
On one typical occasion, I took the car to a local mechanic to investigate a fluid leak. He examined the undercarriage and told me that oil was “leaking from a little box about two-thirds of the way back that seems to have something to do with distributing braking power between front and rear.” He then refused to touch the car and advised me to go to the dealership, fifty miles away. Anyone can work on a Ford or a Honda; that’s why those cars are more convenient and less expensive to own, even though they are equally mechanically complex.
我确实很喜欢那辆车,但我再也不会拥有一辆古怪的车了。有一天,诊断出一个特别昂贵的问题,我受够了标致。我把它送到了当地一家接受汽车捐赠的慈善机构。然后我买了一辆破旧的本田思域,价格大约相当于修理费。
I did love that car, but I will never own a quirky car again. A day came when a particularly expensive problem was diagnosed, and I had had enough of Peugeots. I took it to a local charity that accepted cars as donations. Then I bought a beat-up old Honda Civic for about what the repair would have cost.
领域开发缺乏标准的设计元素,因此每个领域模型和相应的实现都很古怪且难以理解。此外,每个团队都必须重新发明轮子(或齿轮或雨刷)。在面向对象设计的世界中,一切都是对象、引用或消息——当然,这是一种有用的抽象。但这不足以限制领域设计选择的范围,也不支持对领域模型进行经济的讨论。
Standard design elements are lacking for domain development, and so every domain model and corresponding implementation is quirky and hard to understand. Moreover, every team has to reinvent the wheel (or the gear, or the windshield wiper). In the world of object-oriented design, everything is an object, a reference, or a message—which, of course, is a useful abstraction. But that does not sufficiently constrain the range of domain design choices and does not support an economical discussion of a domain model.
如果只说“一切都是对象”,就好比木匠或建筑师用“一切都是房间”来概括房屋。大房间里有高压插座和水槽,你可以在那里做饭。楼上有一个小房间,你可以在那里睡觉。描述一栋普通的房子需要几页纸。建造或使用房屋的人意识到房间遵循着模式,这些模式有着特殊的名字,比如“厨房”。这种语言使得房屋设计的讨论变得经济实惠。
To stop with “Everything is an object” would be like a carpenter or an architect summing up houses by saying “Everything is a room.” There would be the big room with high-voltage outlets and a sink, where you might cook. There would be the small room upstairs, where you might sleep. It would take pages to describe an ordinary house. People who build or use houses realize that rooms follow patterns, patterns with special names, such as “kitchen.” This language enables economical discussion of house design.
此外,并非所有功能组合都是实用的。为什么不设一个可以洗澡和睡觉的房间呢?那样不是很方便吗?但长期的经验已经形成了习惯,我们将“卧室”与“浴室”分开。毕竟,与卧室相比,洗浴设施往往由更多人共用,并且它们需要最大程度的隐私,即使是与同一间卧室的其他人共用。而浴室有专门且昂贵的基础设施要求。浴缸和马桶通常位于同一房间,因为它们都需要相同的基础设施(供水和排水),并且都是私下使用的。
Moreover, not all combinations of functions turn out to be practical. Why not a room where you bathe and sleep? Wouldn’t that be convenient. But long experience has precipitated into custom, and we separate our “bedrooms” from our “bathrooms.” After all, bathing facilities tend to be shared among more people than bedrooms are, and they require maximum privacy, even from the others who share the same bedroom. And bathrooms have specialized and expensive infrastructure requirements. Bathtubs and toilets typically end up in the same room because both require the same infrastructure (water and drainage) and both are used in private.
另一个对基础设施有特殊要求的房间是你可能用来准备饭菜的房间,也称为“厨房”。与浴室不同,厨房没有特殊的隐私要求。由于成本高昂,即使在相对较大的房子里,厨房通常也只有一个。这种独特性也促进了我们共同准备食物和进餐的习俗。
Another room that has special infrastructure requirements is that room where you might prepare meals, also known as the “kitchen.” In contrast to the bathroom, a kitchen has no special privacy requirements. Because of its expense, there is typically only one, even in relatively large houses. This singularity also facilitates our communal food preparation and eating customs.
当我说我想要一套带有开放式厨房的三居室、两浴室的房子时,我已经把大量的信息塞进了一句简短的句子中,并且避免了很多愚蠢的错误——比如把马桶放在冰箱旁边。
When I say that I want a three-bedroom, two-bath house with an open-plan kitchen, I have packed a huge amount of information into a short sentence, and I’ve avoided a lot of silly mistakes—such as putting a toilet next to the refrigerator.
在设计的每个领域——房屋、汽车、划艇或软件——我们都以过去行之有效的模式为基础,进行即兴创作。在既定主题内。有时我们必须发明一些全新的东西。但通过以模式为基础建立标准元素,我们可以避免将精力浪费在已知解决方案的问题上,这样我们就可以专注于我们不同寻常的需求。此外,从传统模式构建有助于我们避免设计出难以沟通的特殊设计。
In every area of design—houses, cars, rowboats, or software—we build on patterns that have been found to work in the past, improvising within established themes. Sometimes we have to invent something completely new. But by basing standard elements on patterns, we avoid wasting our energy on problems with known solutions so that we can focus on our unusual needs. Also, building from conventional patterns helps us avoid a design so idiosyncratic that it is difficult to communicate.
尽管软件领域设计不如其他设计领域那么成熟(并且在任何情况下可能过于多样化而无法容纳像汽车零部件或房间那样具体的模式),但仍然需要超越“一切都是对象”,至少要达到区分螺栓和弹簧的水平。
Although software domain design is not as mature as other design fields—and in any case may be too diverse to accommodate patterns as specific as those used for car parts or rooms—there is nonetheless a need to move beyond “Everything is an object” to at least the equivalent of distinguishing bolts from springs.
20 世纪 70 年代,一群以克里斯托弗·亚历山大 ( Alexander et al. 1977 ) 为首的建筑师提出了一种共享和标准化设计见解的形式。他们的“模式语言”将经过实践检验的、解决常见问题的设计解决方案编织在一起(比我的“厨房”示例要巧妙得多,这可能让亚历山大的一些读者感到不快)。其目的是让建筑商和用户能够用这种语言进行交流,并在模式的指导下,建造出美观、实用且让使用者感觉良好的建筑。
A form for sharing and standardizing design insight was introduced in the 1970s by a group of architects led by Christopher Alexander (Alexander et al. 1977). Their “pattern language” wove together tried-and-true design solutions to common problems (much more subtly than my “kitchen” example, which has probably caused some readers of Alexander to cringe). The intent was that builders and users would communicate in this language, and they would be guided by the patterns to produce beautiful buildings that worked well and felt good to the people who used them.
无论架构师如何看待这个想法,这种模式语言对软件设计产生了巨大的影响。在 20 世纪 90 年代,软件模式被应用于许多方面并取得了一些成功,特别是在详细设计(Gamma 等人,1995 年)和技术架构(Buschmann 等人,1996 年)中。最近,模式已用于记录基本的面向对象设计技术(Larman,1998 年)和企业架构(Fowler,2003 年,Alur 等人,2001 年)。模式语言现在已成为组织软件设计思想的主流技术。
Whatever architects might think of the idea, this pattern language has had a big impact on software design. In the 1990s software patterns were applied in many ways with some success, notably in detailed design (Gamma et al. 1995) and technical architectures (Buschmann et al. 1996). More recently, patterns have been used to document basic object-oriented design techniques (Larman 1998) and enterprise architectures (Fowler 2003, Alur et al. 2001). The language of patterns is now a mainstream technique for organizing software design ideas.
模式名称旨在成为团队语言中的术语,我在本书中也这样使用它们。当模式名称出现在讨论中时,它会被格式化为小写字母以将其调出。
The pattern names are meant to become terms in the language of the team, and I’ve used them that way in this book. When a pattern name appears in a discussion, it is FORMATTED IN SMALL CAPS to call it out.
这是我在本书中格式化模式的方式。这个基本计划有一些变化,因为我更喜欢逐个案例的清晰度和可读性,而不是僵硬的结构。...
Here is how I’ve formatted patterns in this book. There is some variation around this basic plan, as I have favored case-by-case clarity and readability over rigid structure. . . .
[概念说明。有时是视觉隐喻或令人回味的文字。]
[Illustration of concept. Sometimes a visual metaphor or evocative text.]
[上下文。简要说明该概念与其他模式的关系。在某些情况下,简要概述该模式。
[Context. A brief explanation of how the concept relates to other patterns. In some cases, a brief overview of the pattern.
然而,本书中的大部分背景讨论都发生在章节介绍和其他叙述部分,而不是在模式中。
However, much of the context discussion in this book is in the chapter introductions and other narrative segments, rather than within the patterns.
【问题讨论。】
[Problem discussion.]
问题概要。
Problem summary.
对问题解决的讨论促使了解决方案的出现。
Discussion of the resolution of problem forces into a solution.
所以:
Therefore:
解决方案摘要。
Solution summary.
后果。实施注意事项。示例。
Consequences. Implementation considerations. Examples.
最终结果上下文:关于该模式如何导致后续模式的简要解释。
Resulting context: A brief explanation of how the pattern leads to later patterns.
[关于实施挑战的讨论。按照 Alexander 的原始格式,这一讨论将被纳入描述问题解决方案的部分,而我在本书中经常遵循 Alexander 的组织方式。但有些模式需要更长时间的实施讨论。为了保持核心模式讨论的紧凑性,我将这些冗长的实施讨论移到了模式之后。
[Discussion of implementation challenges. In Alexander’s original format, this discussion would have been folded into the section describing the resolution of the problem, and I have often followed Alexander’s organization in this book. But some patterns demand lengthier discussions of implementation. To keep the core pattern discussion tight, I have moved such long implementation discussions out, after the pattern.
此外,冗长的例子,特别是那些结合了多种模式的例子,通常超出了模式的范围。]
Also, lengthy examples, particularly those that combine multiple patterns, are often outside the patterns.]
以下是本书中使用的选定术语、模式名称和其他概念的简要定义。
Here are brief definitions of selected terms, pattern names, and other concepts used in the book.
AGGREGATE关联对象的集群,在数据更改时被视为一个单元。外部引用仅限于 AGGREGATE 的一个成员,该成员被指定为根。在AGGREGATE 的边界内适用一组一致性规则
AGGREGATE A cluster of associated objects that are treated as a unit for the purpose of data changes. External references are restricted to one member of the AGGREGATE, designated as the root. A set of consistency rules applies within the AGGREGATE’S boundaries.
分析模式一组代表业务建模中常见构造的概念。它可能仅与一个领域相关,也可能跨越多个领域(Fowler 1997,第 8 页)。
analysis pattern A group of concepts that represents a common construction in business modeling. It may be relevant to only one domain or may span many domains (Fowler 1997, p. 8).
断言 程序在某一时刻的正确状态的陈述,与程序如何执行无关。通常,断言指定操作的结果或设计元素的不变量。
ASSERTION A statement of the correct state of a program at some point, independent of how it does it. Typically, an ASSERTION specifies the result of an operation or an invariant of a design element.
有界上下文特定模型的有限适用性。有界上下文让团队成员对哪些必须保持一致以及哪些可以独立发展有一个清晰且共同的理解。
BOUNDED CONTEXT The delimited applicability of a particular model. BOUNDING CONTEXTS gives team members a clear and shared understanding of what has to be consistent and what can develop independently.
client A program element that is calling the element under design, using its capabilities.
cohesion Logical agreement and dependence.
命令(又称修饰符)对系统产生某些变化的操作(例如,设置变量)。有意产生副作用的操作。
command (a.k.a. modifier) An operation that effects some change to the system (for example, setting a variable). An operation that intentionally creates a side effect.
概念轮廓领域本身的底层一致性,如果反映在模型中,则可以帮助设计更自然地适应变化。
CONCEPTUAL CONTOUR An underlying consistency of the domain itself, which, if reflected in a model, can help the design accommodate change more naturally.
语境 单词或语句出现的环境,决定了其含义。请参阅有界语境。
context The setting in which a word or statement appears that determines its meaning. See BOUNDED CONTEXT.
上下文图 项目所涉及的BOUNDED CONTEXTS的表示以及它们与其模型之间的实际关系。
CONTEXT MAP A representation of the BOUNDED CONTEXTS involved in a project and the actual relationships between them and their models.
核心领域模型的独特部分,以用户目标为中心,使应用程序与众不同并具有价值。
CORE DOMAIN The distinctive part of the model, central to the user’s goals, that differentiates the application and makes it valuable.
声明式设计一种编程形式,其中属性的精确描述实际上控制着软件。可执行规范。
declarative design A form of programming in which a precise description of properties actually controls the software. An executable specification.
深度模型领域专家主要关注点及其最相关知识的深刻表达。深度模型摆脱了领域肤浅的方面和幼稚的解释。
deep model An incisive expression of the primary concerns of the domain experts and their most relevant knowledge. A deep model sloughs off superficial aspects of the domain and naive interpretations.
设计模式对通信对象和类的描述,这些对象和类是为解决特定环境中的一般设计问题而定制的。(Gamma 等人,1995 年,第 3 页)
design pattern A description of communicating objects and classes that are customized to solve a general design problem in a particular context. (Gamma et al. 1995, p. 3)
蒸馏 分离混合物成分以提取精华的过程,使其更有价值和用途。在软件设计中,抽象模型中的关键方面,或对较大系统进行分区,以突出核心领域。
distillation A process of separating the components of a mixture to extract the essence in a form that makes it more valuable and useful. In software design, the abstraction of key aspects in a model, or the partitioning of a larger system to bring the CORE DOMAIN to the fore.
domain A sphere of knowledge, influence, or activity.
领域专家软件项目的成员,其领域是应用程序领域,而不是软件开发领域。领域专家不是软件的普通用户,而是对该主题有深入的了解。
domain expert A member of a software project whose field is the domain of the application, rather than software development. Not just any user of the software, the domain expert has deep knowledge of the subject.
领域层负责分层架构中领域逻辑的设计和实现部分。领域层是领域模型的软件表达所在。
domain layer That portion of the design and implementation responsible for domain logic within a LAYERED ARCHITECTURE. The domain layer is where the software expression of the domain model lives.
实体 (Entity)从根本上来说,对象不是由其属性来定义的,而是由连续性和同一性的线索来定义的。
ENTITY An object fundamentally defined not by its attributes, but by a thread of continuity and identity.
工厂一种封装复杂创建逻辑和为客户端抽象所创建对象的类型的机制。
FACTORY A mechanism for encapsulating complex creation logic and abstracting the type of a created object for the sake of a client.
function An operation that computes and returns a result without observable side effects.
不可变 (immutable)创建后,可观察状态永远不会改变的属性。
immutable The property of never changing observable state after creation.
隐含概念对于理解模型或设计的含义来说必不可少但从未提及的概念。
implicit concept A concept that is necessary to understand the meaning of a model or design but is never mentioned.
揭示意图的界面在设计中,类、方法和其他元素的名称既传达了原始开发人员创建它们的目的,也传达了它们对客户端开发人员的价值。
INTENTION-REVEALING INTERFACE A design in which the names of classes, methods, and other elements convey both the original developer’s purpose in creating them and their value to a client developer.
不变的断言 关于某些设计元素,这些元素必须始终为真,除了在特定的瞬时情况下,例如方法执行的中间,或未提交的数据库事务的中间。
invariant An ASSERTION about some design element that must be true at all times, except during specifically transient situations such as the middle of the execution of a method, or the middle of an uncommitted database transaction.
iteration A process in which a program is repeatedly improved in small steps. Also, one of those steps.
大型结构一组高级概念、规则或两者,用于为整个系统建立设计模式。一种允许大体讨论和理解系统的语言。
large-scale structure A set of high-level concepts, rules, or both that establishes a pattern of design for an entire system. A language that allows the system to be discussed and understood in broad strokes.
LAYERED ARCHITECTURE A technique for separating the concerns of a software system, isolating a domain layer, among other things.
生命周期对象在创建和删除之间可以采取的一系列状态,通常带有约束以确保从一种状态变为另一种状态时的完整性。可能包括系统和不同有界上下文之间的实体迁移。
life cycle A sequence of states an object can take on between creation and deletion, typically with constraints to ensure integrity when changing from one state to another. May include migration of an ENTITY between systems and different BOUNDED CONTEXTS.
模型描述某个领域的选定方面并可用于解决与该领域相关的问题的抽象系统。
model A system of abstractions that describes selected aspects of a domain and can be used to solve problems related to that domain.
模型驱动设计一种设计,其中软件元素的某些子集与模型元素紧密对应。此外,还是一种共同开发模型和实现的过程,使它们保持一致。
MODEL-DRIVEN DESIGN A design in which some subset of software elements corresponds closely to elements of a model. Also, a process of codeveloping a model and an implementation that stay aligned with each other.
建模范式在某个领域中划分概念的一种特定风格,结合工具来创建这些概念的软件类似物(例如,面向对象编程和逻辑编程)。
modeling paradigm A particular style of carving out concepts in a domain, combined with tools to create software analogs of those concepts (for example, object-oriented programming and logic programming).
存储库一种封装存储、检索和搜索行为的机制,用于模拟对象集合。
REPOSITORY A mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects.
责任执行任务或了解信息的义务(Wirfs-Brock 等人,2003 年,第 3 页)。
responsibility An obligation to perform a task or know information (Wirfs-Brock et al. 2003, p. 3).
SERVICE An operation offered as an interface that stands alone in the model, with no encapsulated state.
副作用由操作导致的任何可观察到的状态变化,无论是有意还是无意的,甚至是故意的更新。
side effect Any observable change of state resulting from an operation, whether intentional or not, even a deliberate update.
无副作用功能 参见 功能。
SIDE-EFFECT-FREE FUNCTION See function.
独立类除了系统原语和基本库之外,不需要参考任何其他类就可以理解和测试的类。
STANDALONE CLASS A class that can be understood and tested without reference to any others, except system primitives and basic libraries.
无国籍 设计元素的属性,允许客户端使用其任何操作,而无需考虑元素的历史记录。无状态元素可以使用全局可访问的信息,甚至可以更改该全局信息(即它可能具有副作用),但不保留影响其行为的私有状态。
stateless The property of a design element that allows a client to use any of its operations without regard to the element’s history. A stateless element may use information that is accessible globally and may even change that global information (that is, it may have side effects) but holds no private state that affects its behavior.
战略设计适用于系统大部分的建模和设计决策。此类决策影响整个项目,必须在团队层面做出决定。
strategic design Modeling and design decisions that apply to large parts of the system. Such decisions affect the entire project and have to be decided at team level.
柔性设计将深度模型固有的功能交到客户端开发人员手中,使其能够做出清晰、灵活的表达,从而稳健地提供预期结果。同样重要的是,它利用相同的深度模型使设计本身易于实施者塑造和重塑,以适应新的见解。
supple design A design that puts the power inherent in a deep model into the hands of a client developer to make clear, flexible expressions that give expected results robustly. Equally important, it leverages that same deep model to make the design itself easy for the implementer to mold and reshape to accommodate new insight.
通用语言一种围绕领域模型构建的语言,由所有团队成员使用,将团队的所有活动与软件连接起来。
UBIQUITOUS LANGUAGE A language structured around the domain model and used by all team members to connect all the activities of the team with the software.
统一模型的内部一致性,使得每个术语都明确并且没有规则相互矛盾。
unification The internal consistency of a model such that each term is unambiguous and no rules contradict.
VALUE OBJECT An object that describes some characteristic or attribute but carries no concept of identity.
WHOLE VALUE An object that models a single, complete concept.
Alexander, C.、M. Silverstein、S. Angel、S. Ishikawa 和 D. Abrams。1975 年。《俄勒冈实验》。牛津大学出版社。
Alexander, C., M. Silverstein, S. Angel, S. Ishikawa, and D. Abrams. 1975. The Oregon Experiment. Oxford University Press.
Alexander, C.、S. Ishikawa 和 M. Silverstein。1977 年。《模式语言:城镇、建筑、施工》。牛津大学出版社。
Alexander, C., S. Ishikawa, and M. Silverstein. 1977. A Pattern Language: Towns, Buildings, Construction. Oxford University Press.
Alur, D.、J. Crupi 和 D. Malks。2001 年。核心 J2EE 模式。Sun Microsystems Press。
Alur, D., J. Crupi, and D. Malks. 2001. Core J2EE Patterns. Sun Microsystems Press.
Beck, K. 1997. Smalltalk 最佳实践模式。Prentice Hall PTR。
Beck, K. 1997. Smalltalk Best Practice Patterns. Prentice Hall PTR.
Beck, K. 2000. 《极限编程解析:拥抱变化》。Addison-Wesley。
Beck, K. 2000. Extreme Programming Explained: Embrace Change. Addison-Wesley.
Beck, K. 2003.测试驱动开发:通过示例。Addison-Wesley。
Beck, K. 2003. Test-Driven Development: By Example. Addison-Wesley.
Buschmann, F.、R. Meunier、H. Rohnert、P. Sommerlad 和 M. Stal。1996 年。面向模式的软件架构:模式系统。Wiley。
Buschmann, F., R. Meunier, H. Rohnert, P. Sommerlad, and M. Stal. 1996. Pattern-Oriented Software Architecture: A System of Patterns. Wiley.
Cockburn, A. 1998.生存面向对象项目:经理指南。Addison-Wesley。
Cockburn, A. 1998. Surviving Object-Oriented Projects: A Manager’s Guide. Addison-Wesley.
Evans, E. 和 M. Fowler。1997 年。“规范。”PLoP 97 会议论文集。
Evans, E., and M. Fowler. 1997. “Specifications.” Proceedings of PLoP 97 Conference.
Fayad, M. 和 R. Johnson。2000 年。领域特定应用程序框架。Wiley。
Fayad, M., and R. Johnson. 2000. Domain-Specific Application Frameworks. Wiley.
Fowler, M. 1997.分析模式:可重用对象模型。Addison-Wesley。
Fowler, M. 1997. Analysis Patterns: Reusable Object Models. Addison-Wesley.
Fowler, M. 1999.重构:改进现有代码的设计。Addison-Wesley。
Fowler, M. 1999. Refactoring: Improving the Design of Existing Code. Addison-Wesley.
Fowler, M. 2003.企业应用程序架构模式。Addison-Wesley。
Fowler, M. 2003. Patterns of Enterprise Application Architecture. Addison-Wesley.
Gamma, E.、R. Helm、R. Johnson 和 J. Vlissides。1995 年。《设计模式》。Addison-Wesley。
Gamma, E., R. Helm, R. Johnson, and J. Vlissides. 1995. Design Patterns. Addison-Wesley.
Kerievsky, J. 2003.“持续学习”,极限编程观点,Michele Marchesi 等,Addison-Wesley。
Kerievsky, J. 2003. “Continuous Learning,” in Extreme Programming Perspectives, Michele Marchesi et al. Addison-Wesley.
Kerievsky, J. 2003.网站:http://www.industriallogic.com/xp/refactoring。
Kerievsky, J. 2003. Web site: http://www.industriallogic.com/xp/refactoring.
Larman, C. 1998.应用 UML 和模式:面向对象分析和设计简介。Prentice Hall PTR。
Larman, C. 1998. Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design. Prentice Hall PTR.
Merriam-Webster. 1993. Merriam-Webster’s Collegiate Dictionary. Tenth edition. Merriam-Webster.
Meyer, B. 1988.面向对象软件构建。Prentice Hall PTR。
Meyer, B. 1988. Object-oriented Software Construction. Prentice Hall PTR.
Murray-Rust,P.、H. Rzepa 和 C. Leach。1995 年。摘要 40。1995年 8 月 21 日在芝加哥举行的第 210 届 ACS 会议上以海报形式展示。http ://www.ch.ic.ac.uk/cml/
Murray-Rust, P., H. Rzepa, and C. Leach. 1995. Abstract 40. Presented as a poster at the 210th ACS Meeting in Chicago on August 21, 1995. http://www.ch.ic.ac.uk/cml/
Pinker, S. 1994.语言本能:大脑如何创造语言。HarperCollins。
Pinker, S. 1994. The Language Instinct: How the Mind Creates Language. HarperCollins.
Succi, GJ、D. Wells、M. Marchesi 和 L. Williams。2002 年。极限编程观点。Pearson Education。
Succi, G. J., D. Wells, M. Marchesi, and L. Williams. 2002. Extreme Programming Perspectives. Pearson Education.
Warmer, J. 和 A. Kleppe。1999。对象约束语言:使用 UML 进行精确建模。Addison-Wesley。
Warmer, J., and A. Kleppe. 1999. The Object Constraint Language: Precise Modeling with UML. Addison-Wesley.
Wirfs-Brock,R.、B. Wilkerson 和 L. Wiener。1990 年。《设计面向对象软件》。Prentice Hall PTR。
Wirfs-Brock, R., B. Wilkerson, and L. Wiener. 1990. Designing Object-Oriented Software. Prentice Hall PTR.
Wirfs-Brock, R. 和 A. McKean。2003 年。对象设计:角色、职责和协作。Addison-Wesley。
Wirfs-Brock, R., and A. McKean. 2003. Object Design: Roles, Responsibilities, and Collaborations. Addison-Wesley.
本书中出现的所有照片均已获得许可使用。
All photographs appearing in this book have been used with permission.
Richard A. Paselk,洪堡州立大学
星盘(第 3 章,第47页)
Richard A. Paselk, Humboldt State University
Astrolabe (Chapter 3, page 47)
© Royalty-Free/Corbis
指纹(第 5 章,第89页)、服务站(第 5 章,第104页)、汽车厂(第 6 章,第136页)、图书管理员(第 6 章,第147页)
© Royalty-Free/Corbis
Fingerprint (Chapter 5, page 89), Service Station (Chapter 5, page 104), Auto Factory (Chapter 6, page 136), Librarian (Chapter 6, page 147)
Martine Jousset
葡萄(第 6 章,第125页)、橄榄树(年轻和年老)(结论,第 500 – 501页)
Martine Jousset
Grapes (Chapter 6, page 125), Olive Trees (young and old)(Conclusion, pages 500–501)
Biophoto Associates/Photo Researchers, Inc.
颤藻的电子显微照片(第 14 章,第335页)
Biophoto Associates/Photo Researchers, Inc.
Electron micrograph of Oscillatoria (Chapter 14, page 335)
Ross J. Venables
赛艇运动员(团体和单人)(第 14 章,第 341页和第 371页)
Ross J. Venables
Rowers (group and single) (Chapter 14, pages 341 and 371)
Photodisc Green/Getty Images
跑步者(第 14 章,第356页)、儿童(第 14 章,第361页)
Photodisc Green/Getty Images
Runners (Chapter 14, page 356), Child (Chapter 14, page 361)
美国国家海洋和大气管理局
中国长城(第 14 章,第364页)
U.S. National Oceanic and Atmospheric Administration
Great Wall of China (Chapter 14, page 364)
© 2003 NAMES 项目基金会,佐治亚州亚特兰大。
摄影师 Paul Margolies。www.aidsquilt.org艾滋病被子
(第 16 章,第439页)
© 2003 NAMES Project Foundation, Atlanta, Georgia.
Photographer Paul Margolies. www.aidsquilt.org
AIDS Quilt (Chapter 16, page 439)
适配器, 367
ADAPTERS, 367
示例,130 – 135、170 – 171、177 – 179
examples, 130–135, 170–171, 177–179
本地身份与全球身份,127
local vs. global identity, 127
所有权关系,126
ownership relationships, 126
敏捷设计
Agile design
蒸馏,483
distillation, 483
模块,111
MODULES, 111
减少依赖,265,435-437,463
reducing dependencies, 265, 435–437, 463
supple design, 243–244, 260–264
艾滋病纪念被子项目,479
AIDS Memorial Quilt Project, 479
分析模式。另请参阅 设计模式。
Analysis patterns. See also design patterns.
定义,293
definition, 293
概述, 294
overview, 294
适配器, 367
ADAPTERS, 367
relationships with external systems, 384–385
建筑框架,70,74,156 – 157,271 – 272,495 – 496
Architectural frameworks, 70, 74, 156–157, 271–272, 495–496
星盘,47
Astrolabe, 47
Awkwardness, concept analysis, 210–216
Bidirectional associations, 102–103
Blind men and the elephant, 378–381
有界上下文。另请参阅 上下文图。
BOUNDED CONTEXT. See also CONTEXT MAP.
代码重用,344
code reuse, 344
CONTINUOUS INTEGRATION, 341–343
定义,382
defining, 382
large-scale structure, 485–488
测试边界,351
testing boundaries, 351
翻译层,374。另请参阅 ANTICORRUPTION LAYER;PUBLISHED LANGUAGE。
translation layers, 374. See also ANTICORRUPTION LAYER; PUBLISHED LANGUAGE.
与 模块, 335
vs. MODULES, 335
Brainstorming, 7–13, 207–216, 219
Breakthroughs, 193–200, 202–203
业务逻辑,在用户界面层,77
Business logic, in user interface layer, 77
回调, 73
Callbacks, 73
货物运输示例。请参阅 示例,货物运输。
Cargo shipping examples. See examples, cargo shipping.
更改设计。参见 重构。
Changing the design. See refactoring.
Chemical warehouse packer example, 235–241
化学示例,377
Chemistry example, 377
克里斯,约翰,5岁
Cleese, John, 5
CLOSURE OF OPERATIONS, 268–270
代码作为文档,40
Code as documentation, 40
代码重用
Code reuse
有界上下文, 344
BOUNDED CONTEXT, 344
Cohesion, MODULES, 109–110, 113
凝聚机制
COHESIVE MECHANISMS
and declarative style, 426–427
与 通用子域名, 425
vs. GENERIC SUBDOMAINS, 425
Common language. See PUBLISHED LANGUAGE; UBIQUITOUS LANGUAGE.
交流,演讲。参见 “无处不在的语言”。
Communication, speech. See UBIQUITOUS LANGUAGE.
书面沟通。参见 文档;UML(统一建模语言);通用语言。
Communication, written. See documents; UML (Unified Modeling Language); UBIQUITOUS LANGUAGE.
复杂性,减少。参见 提炼;大规模结构;分层架构; 柔性设计。
Complexity, reducing. See distillation; large-scale structure; LAYERED ARCHITECTURE; supple design.
Composite SPECIFICATION, 273–282
Concept analysis. See also analysis patterns; examples, concept analysis.
language of the domain experts, 206–207
processes as domain objects, 222–223
researching existing resources, 217–219
规格,223
SPECIFICATION, 223
反复试验,219
trial and error, 219
Conceptual layers, See LAYERED ARCHITECTURE; RESPONSIBILITY LAYERS
Configuring SPECIFICATION, 226–227
构造函数,141 – 142,174 – 175。另 请参阅工厂。
Constructors, 141–142, 174–175. See also FACTORIES.
C ONTEXT MAP。另请参阅 BOUNDED CONTEXT。
CONTEXT MAP. See also BOUNDED CONTEXT.
organizing and documenting, 351–352
与大规模结构相比,446、485-488
vs. large-scale structure, 446, 485–488
上下文图,选择策略
CONTEXT MAP, choosing a strategy
CUSTOMER/SUPPLIER DEVELOPMENT TEAMS, 356–360
定义有界上下文, 382
defining BOUNDED CONTEXT, 382
部署, 387
deployment, 387
合并OPEN HOST SERVICE和PUBLISHED LANGUAGE,394 – 396
merging OPEN HOST SERVICE and PUBLISHED LANGUAGE, 394–396
merging SEPARATE WAYS and SHARED KERNEL, 389–391
merging SHARED KERNEL and CONTINUOUS INTEGRATION, 391–393
包装,387
packaging, 387
phasing out legacy systems, 393–394
for a project in progress, 388–389
specialized terminologies, 386–387
团队背景, 382
team context, 382
权衡,387
trade-offs, 387
转型,389
transformations, 389
transforming boundaries, 382–383
上下文原则,328 – 329。另请参阅 有界上下文;上下文图。
Context principle, 328–329. See also BOUNDED CONTEXT; CONTEXT MAP.
连续积分,341 – 343,391 – 393。另请参阅积分。
CONTINUOUS INTEGRATION, 341–343, 391–393. See also integration.
Contradictions, concept analysis, 216–217
核心领域
CORE DOMAIN
DOMAIN VISION STATEMENT, 415–416
flagging key elements, 419–420
机制, 425
MECHANISMS, 425
Costs of architecture dictated MODULES, 114–115
以客户为中心的团队,492
Customer-focused teams, 492
数据库调优,示例,102
Database tuning, example, 102
Declarative style of design, 273–282, 426–427
与客户端解耦,156
Decoupling from the client, 156
深度模型
Deep models
Deployment, 387. See also MODULES.
设计变更。请参阅 重构。
Design changes. See refactoring.
设计模式。另请参阅 分析模式。
Design patterns. See also analysis patterns.
轻量级, 320
FLYWEIGHT, 320
与领域模式相比, 309
vs. domain patterns, 309
开发团队。请参阅 团队。
Development teams. See teams.
图表。参见 文档;UML(统一建模语言)。
Diagrams. See documents; UML (Unified Modeling Language).
蒸馏。另请参阅 示例,蒸馏。
Distillation. See also examples, distillation.
DOMAIN VISION STATEMENT, 415–416
INTENTION-REVEALING INTERFACES, 422–427
大尺度结构,483,488-489
large-scale structure, 483, 488–489
重构目标, 437
refactoring targets, 437
设计中的作用,329
role in design, 329
separating CORE concepts, 428–434
蒸馏、粘结机制
Distillation, COHESIVE MECHANISMS
and declarative style, 426–427
与 通用子域名, 425
vs. GENERIC SUBDOMAINS, 425
蒸馏,核心领域
Distillation, CORE DOMAIN
DOMAIN VISION STATEMENT, 415–416
flagging key elements, 419–420
机制, 425
MECHANISMS, 425
提炼,通用子域名
Distillation, GENERIC SUBDOMAINS
调整已发布的设计,408
adapting a published design, 408
现成的解决方案,407
off-the-shelf solutions, 407
概述, 406
overview, 406
与 凝聚机制425
vs. COHESIVE MECHANISMS, 425
Distillation document, 418–419, 420–421
代码作为文档,40
code as documentation, 40
distillation document, 418–419, 420–421
DOMAIN VISION STATEMENT, 415–416
gathering requirements from. See concept analysis; knowledge crunching.
language of, 206–207. See also UBIQUITOUS LANGUAGE.
域对象,生命周期,123 – 124。另请参阅 AGGREGATES;FACTORIES;REPOSITORIES。
Domain objects, life cycle, 123–124. See also AGGREGATES; FACTORIES; REPOSITORIES.
领域模式与设计模式,309
Domain patterns vs. design pattern, 309
DOMAIN VISION STATEMENT, 415–416
Domain-specific language, 272–273
Elephant and the blind men, 378–381
封装。另请参阅 FACTORIES。
Encapsulation. See also FACTORIES.
意图揭示界面, 246
INTENTION-REVEALING INTERFACES, 246
储存库, 154
REPOSITORIES, 154
ENTITIES. See also associations; SERVICES; VALUE OBJECTS.
聚类。请参阅 AGGREGATES。
clustering. See AGGREGATES.
ID 唯一性,96
ID uniqueness, 96
referencing with VALUE OBJECTS, 98–99
与Java 实体 bean相比, 91
vs. Java entity beans, 91
例子
Examples
chemical warehouse packer, 235–241
化学,已发表语言,377
chemistry, PUBLISHED LANGUAGE, 377
CLOSURE OF OPERATIONS, 269–270
composite SPECIFICATION, 278–282
extracting hidden concepts, 17–20
integration with other systems, 372–373
INTENTION-REVEALING INTERFACES, 423–424
introducing new features, 181–185
package coding in Java, 111–112
油漆混合应用,247 – 249,252 – 254,256 – 259
paint-mixing application, 247–249, 252–254, 256–259
PLUGGABLE COMPONENT FRAMEWORK, 475–479
已出版语言, 377
PUBLISHED LANGUAGE, 377
purchase order integrity, 130–135
RESPONSIBILITY LAYERS, 452–460
selecting from Collections, 269–270
SEMATECH CIM framework, 476–479
SIDE-EFFECT-FREE FUNCTIONS, 252–254, 285–286
调整数据库, 102
tuning a database, 102
值对象,102
VALUE OBJECTS, 102
预订
booking
extracting hidden concepts, 17–20
概念分析,222
concept analysis, 222
extracting hidden concepts, 17–20
identifying missing concepts, 207–210
large-scale structure, 452–460
multiple development teams, 358–360
RESPONSIBILITY LAYERS, 452–460
shipping operations and routes, 41–43
extracting hidden concepts, 17–20
identifying missing concepts, 207–210
researching existing resources, 217–219
resolving awkwardness, 211–215
COHESIVE MECHANISMS, 423–424, 425–427
organization chart, 423–424, 425–427
示例、集成
Examples, integration
Examples, large-scale structure
PLUGGABLE COMPONENT FRAMEWORK, 475–479
RESPONSIBILITY LAYERS, 452–460
Examples, LAYERED ARCHITECTURE
partitioning applications, 71–72
RESPONSIBILITY LAYERS, 452–460
concept analysis, 211–215, 217–219
利息计算器,211 – 215,217 – 219,295 – 306
interest calculator, 211–215, 217–219, 295–306
Explicit constraints, concept analysis, 220–222
External systems, 383–385. See also integration.
Extracting hidden concepts, 17–20. See also implicit concepts.
设施,194
Facilities, 194
configuring SPECIFICATION, 226–227
设计界面,143
designing the interface, 143
ENTITY vs. VALUE OBJECT, 144–145
不变逻辑,143
invariant logic, 143
要求, 139
requirements, 139
电影剪辑轶事,5
Film editing anecdote, 5
灵活性。参见 柔性设计。
Flexibility. See supple design.
F LYWEIGHT图案,320
FLYWEIGHT pattern, 320
Functions, SIDE-EFFECT-FREE, 250–254, 285–286
通用子域名
GENERIC SUBDOMAINS
调整已发布的设计,408
adapting a published design, 408
现成的解决方案,407
off-the-shelf solutions, 407
概述, 406
overview, 406
与 凝聚机制425
vs. COHESIVE MECHANISMS, 425
粒度,108
Granularity, 108
Hidden concepts, extracting, 17–20
圣杯轶事,5
Holy Grail anecdote, 5
身份
Identity
本地与全球,127
local vs. global, 127
Immutability of VALUE OBJECTS, 100–101
基础设施层,70
Infrastructure layer, 70
Infrastructure-driven packaging, 112–116
In-house solution, GENERIC SUB DOMAINS, 409–410
Insurance project example, 372–373
CONTINUOUS INTEGRATION, 341–343, 391–393
cost/benefit analysis, 371–373
elephant and the blind men, 378–381
开放式主机服务,374
OPEN HOST SERVICE, 374
translation layers, 374. See also PUBLISHED LANGUAGE.
完整性。请参见 模型完整性。
Integrity. See model integrity.
INTENTION-REVEALING INTERFACES, 246–249, 422–427
利息计算器示例,211 – 215,217 – 219,295 – 306
Interest calculator examples, 211–215, 217–219, 295–306
Internet Explorer 书签轶事,57 – 59
Internet Explorer bookmark anecdote, 57–59
Inventory management example, 504–505
投资银行示例,194 – 200,211 – 215,501
Investment banking example, 194–200, 211–215, 501
Isolated domain layer, 106–107
Isolating the domain. See ANTICORRUPTION LAYER; distillation; LAYERED ARCHITECTURE.
迭代设计过程,14,188,445
Iterative design process, 14, 188, 445
Jargon. See PUBLISHED LANGUAGE; UBIQUITOUS LANGUAGE.
Java 实体 bean与 ENTITIES , 91
Java entity beans vs. ENTITIES, 91
Knowledge crunching, example, 7–12
Language of the domain experts, 206–207
大规模结构。另请参阅 提炼;示例、大规模结构;分层架构;战略设计。
Large-scale structure. See also distillation; examples, large-scale structure; LAYERED ARCHITECTURE; strategic design.
上下文地图,446
CONTEXT MAP, 446
定义,442
definition, 442
development constraints, 445–446
极简主义,481
minimalism, 481
PLUGGABLE COMPONENT FRAMEWORK, 475–479
重构,481
refactoring, 481
设计中的作用,329
role in design, 329
团队沟通,482
team communication, 482
大型结构,职责层
Large-scale structure, RESPONSIBILITY LAYERS
有用的特性,461
useful characteristics, 461
LAYERED ARCHITECTURE. See also distillation; examples, LAYERED ARCHITECTURE; large-scale structure.
回调, 73
callbacks, 73
概念层,70
conceptual layers, 70
图表,68
diagram, 68
基础设施层, 70
infrastructure layer, 70
isolated domain layer, 106–107
MVC(模型-视图-控制器), 73
MVC (MODEL-VIEW-CONTROLLER), 73
观察员, 73
OBSERVERS, 73
划分复杂程序,70
partitioning complex programs, 70
separating user interface, application, and domain, 76–79
智能用户界面, 73
SMART UI, 73
交易脚本, 79
TRANSACTION SCRIPT, 79
user interface layer, 70, 76–79
价值,69
value of, 69
分层架构,防腐层
LAYERED ARCHITECTURE, ANTICORRUPTION LAYER
适配器, 367
ADAPTERS, 367
relationships with external systems, 384–385
分层架构、职责层
LAYERED ARCHITECTURE, RESPONSIBILITY LAYERS
有用的特性,461
useful characteristics, 461
Legacy systems, phasing out, 393–394
域对象的生命周期,123 – 124。另请参阅 AGGREGATES;FACTORIES;REPOSITORIES 。
Life cycle of domain objects, 123–124. See also AGGREGATES; FACTORIES; REPOSITORIES.
贷款管理示例。请参阅 示例、贷款管理。
Loan management examples. See examples, loan management.
本地身份与全球身份,127
Local vs. global identity, 127
合并
Merging
OPEN HOST SERVICE and PUBLISHED LANGUAGE, 394–396
SEPARATE WAYS to SHARED KERNEL, 389–391
SHARED KERNEL to CONTINUOUS INTEGRATION, 391–393
元数据映射层, 149
METADATA MAPPING LAYERS, 149
错误身份轶事,89
Mistaken identity anecdote, 89
模型完整性。另请参阅 BOUNDED CONTEXT;CONTEXT MAP;多重模型。
Model integrity. See also BOUNDED CONTEXT; CONTEXT MAP; multiple models.
establishing boundaries, 333–334
多个模型, 333
multiple models, 333
recognizing relationships, 333–334
unification, 332. See also CONTINUOUS INTEGRATION.
模型层。请参阅 领域层。
Model layer. See domain layer.
基于模型的语言。参见 UBIQUITOUS LANGUAGE。
Model-based language. See UBIQUITOUS LANGUAGE.
correspondence to design, 50–51
概述, 49
overview, 49
模型的相关性, 49
relevance of model, 49
造型
Modeling
integrating with programming, 60–62
楷模
Models
绑定到实现。请参阅 模型驱动设计。
binding to implementation. See MODEL-DRIVEN DESIGN.
模型-视图-控制器(MVC), 73
MODEL-VIEW-CONTROLLER (MVC), 73
敏捷,111
agile, 111
确定含义,110
determining meaning of, 110
infrastructure-driven packaging, 112–116
命名,110
naming, 110
概述, 109
overview, 109
封装域对象, 115
packaging domain objects, 115
与 有界上下文335
vs. BOUNDED CONTEXT, 335
蒙提·派森轶事,5
Monty Python anecdote, 5
MVC(模型-视图-控制器), 73
MVC (MODEL-VIEW-CONTROLLER), 73
命名
Naming
有界上下文, 345
BOUNDED CONTEXTS, 345
柔性设计惯例,247
conventions for supple design, 247
意图揭示界面,247
INTENTION-REVEALING INTERFACES, 247
模块, 110
MODULES, 110
服务, 105
SERVICES, 105
对象引用。请参阅 存储库。
Object references. See REPOSITORIES.
Objects. See also ENTITIES; VALUE OBJECTS.
creating, 234–235. See also constructors; FACTORIES.
designing for relational databases, 159–161
made up of objects. See AGGREGATES; COMPOSITE.
观察员73
OBSERVERS, 73
现成的解决方案,407
Off-the-shelf solutions, 407
O PEN HOST SERVICE,转换为已发布的语言,394 – 396
OPEN HOST SERVICE, converting to PUBLISHED LANGUAGE, 394–396
Overbooking examples, 18–19, 222
Packaging. See deployment; MODULES.
油漆混合应用,示例,247 – 249 , 252 – 254 , 256 – 259
Paint-mixing application, examples, 247–249, 252–254, 256–259
分区
Partitioning
complex programs. See large-scale structure; LAYERED ARCHITECTURE.
服务分层,107
SERVICES into layers, 107
模式,507 – 510。另请参阅 分析模式;设计模式;大规模结构。
Patterns, 507–510. See also analysis patterns; design patterns; large-scale structure.
PCB design anecdote, 7–13, 501
Performance tuning, example, 185–186
PLUGGABLE COMPONENT FRAMEWORK, 475–479
P OLICY模式。请参见 策略模式。
POLICY pattern. See STRATEGY pattern.
表示层。请参阅 用户界面层。
Presentation layer. See user interface layer.
Procedural languages, and MODEL-DRIVEN DESIGN, 51–54
Processes as domain objects, 222–223
elephant and the blind men, 378–381
例如,377
example, 377
与OPEN HOST SERVICE合并,394 – 396
merging with OPEN HOST SERVICE, 394–396
被子项目,479
Quilt project, 479
定义,188
definition, 188
为开发人员设计,324
designing for developers, 324
蒸馏,437
distillation, 437
示例,177 – 179、181 – 185、194 – 200、247 – 249
examples, 177–179, 181–185, 194–200, 247–249
大型结构,481
large-scale structure, 481
柔韧设计,191
supple design, 191
重构目标,437
Refactoring targets, 437
参考对象。请参阅 实体。
Reference objects. See ENTITIES.
优势,152
advantages, 152
architectural frameworks, 156–157
与客户端解耦,156
decoupling from the client, 156
designing objects for relational databases, 159–161
封装, 154
encapsulation, 154
元数据映射层, 149
METADATA MAPPING LAYERS, 149
对预先存在的域对象的引用,149
references to preexisting domain objects, 149
事务控制, 156
transaction control, 156
瞬态对象, 149
transient objects, 149
Requirements gathering. See concept analysis; knowledge crunching; UBIQUITOUS LANGUAGE.
有用的特性,461
useful characteristics, 461
重用代码
Reusing code
有界上下文, 344
BOUNDED CONTEXT, 344
Selecting objects, 229–234, 269–270
SEPARATE WAYS, 384–385, 389–391
SERVICES. See also ENTITIES; VALUE OBJECTS.
访问, 108
access to, 108
粒度,108
granularity, 108
and the isolated domain layer, 106–107
命名,105
naming, 105
分层划分,107
partitioning into layers, 107
例如,359
example, 359
merging with CONTINUOUS INTEGRATION, 391–393
merging with SEPARATE WAYS, 389–391
Sharing VALUE OBJECTS, 100–101
运输示例。参见 示例,货物运输。
Shipping examples. See examples, cargo shipping.
Side effects, 250. See also ASSERTIONS.
SIDE-EFFECT-FREE FUNCTIONS, 250–254, 285–286
Simplifying your design. See distillation; large-scale structure; LAYERED ARCHITECTURE.
智能用户界面, 73
SMART UI, 73
SPECIFICATION. See also analysis patterns; design patterns.
申请,227
applying, 227
业务规则,225
business rules, 225
合并。请参阅 复合SPECIFICATION。
combining. See composite SPECIFICATION.
实施,227
implementing, 227
目的,227
purpose, 227
validating objects, 227, 228–229
言语,通用语言。参见 通用语言。
Speech, common language. See UBIQUITOUS LANGUAGE.
Speech, modeling through, 30–32
战略设计。另请参阅 大规模结构。
Strategic design. See also large-scale structure.
评估形势,490
assessing the situation, 490
以客户为中心的架构团队,492
customer-focused architecture teams, 492
开发人员的角色494
developers, role of, 494
essential requirements, 492–495
进化,493
evolution, 493
进化秩序, 491
EVOLVING ORDER, 491
反馈过程,493
feedback process, 493
多个开发团队,491
multiple development teams, 491
对象,作用,494
objects, role of, 494
团队沟通,492
team communication, 492
团队构成,494
team makeup, 494
CLOSURE OF OPERATIONS, 268–270
composite SPECIFICATION, 273–282
declarative style of design, 273–282
domain-specific language, 272–273
INTENTION-REVEALING INTERFACES, 246–249
large-scale structure, 480–483
命名约定, 247
naming conventions, 247
SIDE-EFFECT-FREE FUNCTIONS, 250–254, 285–286
团队背景,382
Team context, 382
选择策略,382
choosing a strategy, 382
通信、大型结构、482
communication, large-scale structure, 482
以客户为中心,492
customer-focused, 492
定义有界上下文, 382
defining BOUNDED CONTEXT, 382
developer community, maturity of, 117–119
团队和战略设计
Teams, and strategic design
沟通,492
communication, 492
以客户为中心,492
customer-focused, 492
开发人员的角色494
developers, role of, 494
化妆, 494
makeup of, 494
多支球队,491
multiple teams, 491
团队,多个
Teams, multiple
战略设计,491
strategic design, 491
Terminology. See BOUNDED CONTEXT; PUBLISHED LANGUAGE; UBIQUITOUS LANGUAGE.
测试边界,351
Testing boundaries, 351
事务控制, 156
Transaction control, 156
交易脚本, 79
TRANSACTION SCRIPT, 79
变换,389
Transformations, 389
Transforming boundaries, 382–383
瞬态对象,149
Transient objects, 149
翻译层,374
Translation layers, 374
调整数据库,示例,102
Tuning a database, example, 102
U双语语言。另请参阅 已出版语言。
UBIQUITOUS LANGUAGE. See also PUBLISHED LANGUAGE.
designing objects for relational databases, 160–161
domain-specific language, 272–273
language of the domain experts, 206–207
specialized terminologies, 386–387
需求分析, 25
requirements analysis, 25
UML (Unified Modeling Language), 35–37
Unification, 332. See also CONTINUOUS INTEGRATION.
Unified Modeling Language (UML), 35–37
更新设计。参见 重构。
Updating the design. See refactoring.
业务逻辑, 77
business logic, 77
定义,70
definition, 70
separating from application and domain, 76–79
Validating objects, 227, 228–229
VALUE OBJECTS. See also ENTITIES; SERVICES.
bidirectional associations, 102–103
变更管理,101
change management, 101
聚类。请参阅 AGGREGATES。
clustering. See AGGREGATES.
作为参数传递, 99
passing as parameters, 99
调整数据库,示例,102
tuning a database, example, 102
愿景声明。请参阅 领域愿景声明。
Vision statement. See DOMAIN VISION STATEMENT.
Vocabulary. See PUBLISHED LANGUAGE; UBIQUITOUS LANGUAGE.
瀑布设计方法,14
Waterfall design method, 14
1. Brian Marick 向我提到了这个例子。
1. Brian Marick mentioned this example to me.
1.模型ENTITY与 Java“实体 bean”不同。实体 bean或多或少是作为实现ENTITIES的框架而设计的,但实际并非如此。大多数ENTITIES都是作为普通对象实现的。无论如何实现,ENTITIES都是域模型中的一个根本区别。
1. A model ENTITY is not the same thing as a Java “entity bean.” Entity beans were meant as a framework for implementing ENTITIES, more or less, but it hasn’t worked out that way. Most ENTITIES are implemented as ordinary objects. Regardless of how they are implemented, ENTITIES are a fundamental distinction in a domain model.
2.WHOLE VALUE模式,作者:Ward Cunningham。
2. The WHOLE VALUE pattern, by Ward Cunningham.
1. David Siegel 在 20 世纪 90 年代设计并使用了该系统,但尚未发布。
1. David Siegel devised and used this system on projects in the 1990s but has not published it.
1. Gamma 等人(1995 年)曾简要提到模式作为重构的目标。Joshua Kerievsky 将重构模式发展为一种更成熟、更实用的形式(Kerievsky 2003 年)。
1. Patterns as targets for refactoring were briefly mentioned in Gamma et al. (1995). Joshua Kerievsky has developed refactoring to patterns into a more mature and useful form (Kerievsky 2003).
1.WHOLE VALUE模式,作者:Ward Cunningham。
1. The WHOLE VALUE pattern, by Ward Cunningham.
1.里根翻译了一句古老的俄罗斯谚语,这句话概括了双方问题的核心——这是另一个弥合分歧的隐喻。
1. Reagan translated an old Russian saying that summed up the heart of the matter for both sides—another metaphor for bridging contexts.
1.当我听到 Ward Cunningham 在一次研讨会讲座上用这个防火墙例子时,我终于明白了“系统隐喻”的含义。
1. SYSTEM METAPHOR finally made sense to me when I heard Ward Cunningham use this firewall example in a workshop lecture.
2. P OSA是面向模式的软件架构的缩写,由 Buschmann 等人于 1996 年提出。
2. POSA is short for Pattern-Oriented Software Architecture, by Buschmann et al. 1996.